diff --git a/articles/postgresql-aggregates-with-rust.md b/articles/postgresql-aggregates-with-rust.md index 6028de2627..a1d901999c 100644 --- a/articles/postgresql-aggregates-with-rust.md +++ b/articles/postgresql-aggregates-with-rust.md @@ -47,7 +47,7 @@ CREATE AGGREGATE example_sum(integer) ); SELECT example_sum(value) FROM UNNEST(ARRAY [1, 2, 3]) as value; --- example_sum +-- example_sum -- ------------- -- 6 -- (1 row) @@ -91,7 +91,7 @@ CREATE AGGREGATE example_sum(integer) ); SELECT example_sum(value) FROM generate_series(0, 4000) as value; --- example_sum +-- example_sum -- ------------- -- 8002000 -- (1 row) @@ -128,7 +128,7 @@ CREATE AGGREGATE example_uniq(text) ); SELECT example_uniq(value) FROM UNNEST(ARRAY ['a', 'a', 'b']) as value; --- example_uniq +-- example_uniq -- -------------- -- 2 -- (1 row) @@ -162,7 +162,7 @@ SELECT example_concat(first, second, third) FROM UNNEST(ARRAY ['a', 'b', 'c']) as first, UNNEST(ARRAY ['1', '2', '3']) as second, UNNEST(ARRAY ['!', '@', '#']) as third; --- example_concat +-- example_concat -- --------------------------------------------------------------------------------------------------------------- -- {a1!,a2!,a3!,b1!,b2!,b3!,c1!,c2!,c3!,a1@,a2@,a3@,b1@,b2@,b3@,c1@,c2@,c3@,a1#,a2#,a3#,b1#,b2#,b3#,c1#,c2#,c3#} -- (1 row) @@ -174,7 +174,7 @@ See how we see `a1`, `b1`, and `c1`? Multiple arguments might not work as you ex SELECT UNNEST(ARRAY ['a', 'b', 'c']) as first, UNNEST(ARRAY ['1', '2', '3']) as second, UNNEST(ARRAY ['!', '@', '#']) as third; --- first | second | third +-- first | second | third -- -------+--------+------- -- a | 1 | ! -- b | 2 | @ @@ -202,7 +202,7 @@ It includes: If a Rust toolchain is not already installed, please follow the instructions on [rustup.rs][rustup-rs]. -You'll also [need to make sure you have some development libraries][pgrx-system-requirements] like `zlib` and `libclang`, as +You'll also [need to make sure you have some development libraries][pgrx-system-requirements] like `zlib` and `libclang`, as `cargo pgrx init` will, by default, build it's own development PostgreSQL installs. Usually it's possible to figure out if something is missing from error messages and then discover the required package for the system. @@ -244,7 +244,7 @@ running SQL generator psql (13.5) Type "help" for help. -exploring_aggregates=# +exploring_aggregates=# ``` Observing the start of the `src/lib.rs` file, we can see the `pg_module_magic!()` and a function `hello_exploring_aggregates`: @@ -268,13 +268,13 @@ CREATE EXTENSION exploring_aggregates; \dx+ exploring_aggregates -- Objects in extension "exploring_aggregates" --- Object description +-- Object description -- --------------------------------------- -- function hello_exploring_aggregates() -- (1 row) SELECT hello_exploring_aggregates(); --- hello_exploring_aggregates +-- hello_exploring_aggregates -- ----------------------------- -- Hello, exploring_aggregates -- (1 row) @@ -337,7 +337,7 @@ running SQL generator This creates `sql/exploring_aggregates-0.0.0.sql`: ```sql -/* +/* This file is auto generated by pgrx. The ordering of items is not stable, it is driven by a dependency graph. @@ -378,6 +378,10 @@ but it should be flexible enough for any use. Aggregates in `pgrx` are defined by creating a type (this doesn't necessarily need to be the state type), then using the [`#[pg_aggregate]`][pgrx-pg_aggregate] procedural macro on an [`pgrx::Aggregate`][pgrx-aggregate-aggregate] implementation for that type. +The aggregate name is specified through a type parameter that implements the `ToAggregateName` trait. +Or you can use a derive-macro `AggregateName` to automatically implement this trait for some type. +The default name of the aggregate will be taken as the name of the structure, but you can change this with the `aggregate_name` attribute + The [`pgrx::Aggregate`][pgrx-aggregate-aggregate] trait has quite a few items (`fn`s, `const`s, `type`s) that you can implement, but the procedural macro can fill in stubs for all non-essential items. The state type (the implementation target by default) must have a [`#[derive(PostgresType)]`][pgrx-postgrestype] declaration, or be a type PostgreSQL already knows about. @@ -390,13 +394,14 @@ use serde::{Serialize, Deserialize}; pg_module_magic!(); -#[derive(Copy, Clone, Default, Debug, PostgresType, Serialize, Deserialize)] +#[derive(Copy, Clone, Default, Debug, PostgresType, Serialize, Deserialize, AggregateName)] +#[aggregate_name = "DemoSum"] pub struct DemoSum { count: i32, } #[pg_aggregate] -impl Aggregate for DemoSum { +impl Aggregate for DemoSum { const INITIAL_CONDITION: Option<&'static str> = Some(r#"{ "count": 0 }"#); type Args = i32; fn state( @@ -413,7 +418,7 @@ impl Aggregate for DemoSum { We can review the generated SQL (generated via `cargo pgrx schema`): ```sql -/* +/* This file is auto generated by pgrx. The ordering of items is not stable, it is driven by a dependency graph. @@ -495,7 +500,7 @@ running SQL generator psql (13.5) Type "help" for help. -exploring_aggregates=# +exploring_aggregates=# ``` Now we're connected via `psql`: @@ -505,7 +510,7 @@ CREATE EXTENSION exploring_aggregates; -- CREATE EXTENSION SELECT DemoSum(value) FROM generate_series(0, 4000) as value; --- demosum +-- demosum -- ------------------- -- {"count":8002000} -- (1 row) @@ -518,11 +523,11 @@ Pretty cool! Let's change the [`State`][pgrx-aggregate-aggregate-state] this time: ```rust -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Default, Debug, AggregateName)] pub struct DemoSum; #[pg_aggregate] -impl Aggregate for DemoSum { +impl Aggregate for DemoSum { const INITIAL_CONDITION: Option<&'static str> = Some(r#"0"#); type Args = i32; type State = i32; @@ -542,7 +547,7 @@ Now when we run it: ```sql SELECT DemoSum(value) FROM generate_series(0, 4000) as value; --- demosum +-- demosum -- --------- -- 8002000 -- (1 row) @@ -552,7 +557,7 @@ This is a fine reimplementation of `SUM` so far, but as we saw previously we nee ```rust #[pg_aggregate] -impl Aggregate for DemoSum { +impl Aggregate for DemoSum { // ... fn combine( mut first: Self::State, @@ -565,35 +570,6 @@ impl Aggregate for DemoSum { } ``` -We can also change the name of the generated aggregate, or set the [`PARALLEL`][pgrx-aggregate-aggregate-parallel] settings, for example: - -```rust -#[pg_aggregate] -impl Aggregate for DemoSum { - // ... - const NAME: &'static str = "demo_sum"; - const PARALLEL: Option = Some(pgrx::aggregate::ParallelOption::Unsafe); - // ... -} -``` - -This generates: - -```sql --- src/lib.rs:9 --- exploring_aggregates::DemoSum -CREATE AGGREGATE demo_sum ( - integer /* i32 */ -) -( - SFUNC = "demo_sum_state", /* exploring_aggregates::DemoSum::state */ - STYPE = integer, /* i32 */ - COMBINEFUNC = "demo_sum_combine", /* exploring_aggregates::DemoSum::combine */ - INITCOND = '0', /* exploring_aggregates::DemoSum::INITIAL_CONDITION */ - PARALLEL = UNSAFE /* exploring_aggregates::DemoSum::PARALLEL */ -); -``` - ## Rust state types It's possible to use a non-SQL (say, [`HashSet`][std::collections::HashSet]) type as a state by using [`Internal`][pgrx::datum::Internal]. @@ -608,11 +584,11 @@ use std::collections::HashSet; pg_module_magic!(); -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Default, Debug, AggregateName)] pub struct DemoUnique; #[pg_aggregate] -impl Aggregate for DemoUnique { +impl Aggregate for DemoUnique { type Args = &'static str; type State = Internal; type Finalize = i32; @@ -656,7 +632,7 @@ We can test it: ```sql SELECT DemoUnique(value) FROM UNNEST(ARRAY ['a', 'a', 'b']) as value; --- demounique +-- demounique -- ------------ -- 2 -- (1 row) @@ -674,11 +650,11 @@ PostgreSQL also supports what are called [*Ordered-Set Aggregates*][postgresql-o Let's create a simple `percentile_disc` reimplementation to get an idea of how to make one with `pgrx`. You'll notice we add [`ORDERED_SET = true`][pgrx::aggregate::Aggregate::ORDERED_SET] and set an (optional) [`OrderedSetArgs`][pgrx::aggregate::Aggregate::OrderedSetArgs], which determines the direct arguments. ```rust -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Default, Debug, AggregateName)] pub struct DemoPercentileDisc; #[pg_aggregate] -impl Aggregate for DemoPercentileDisc { +impl Aggregate for DemoPercentileDisc { type Args = name!(input, i32); type State = Internal; type Finalize = i32; @@ -732,13 +708,13 @@ We can test it like so: ```sql SELECT DemoPercentileDisc(0.5) WITHIN GROUP (ORDER BY income) FROM UNNEST(ARRAY [6000, 70000, 500]) as income; --- demopercentiledisc +-- demopercentiledisc -- -------------------- -- 6000 -- (1 row) SELECT DemoPercentileDisc(0.05) WITHIN GROUP (ORDER BY income) FROM UNNEST(ARRAY [5, 100000000, 6000, 70000, 500]) as income; --- demopercentiledisc +-- demopercentiledisc -- -------------------- -- 5 -- (1 row) @@ -845,7 +821,7 @@ SELECT demo_sum(value) OVER ( -- LOG: moving_state(0, 20) -- LOG: moving_state(0, 300) -- LOG: moving_state(0, 4000) --- demo_sum +-- demo_sum -- ---------- -- 1 -- 20 @@ -864,7 +840,7 @@ SELECT demo_sum(value) OVER ( -- LOG: moving_state(1, 20) -- LOG: moving_state(21, 300) -- LOG: moving_state(321, 4000) --- demo_sum +-- demo_sum -- ---------- -- 4321 -- 4321 @@ -881,7 +857,7 @@ SELECT demo_sum(value) OVER ( -- LOG: moving_state(20, 300) -- LOG: moving_state_inverse(320, 20) -- LOG: moving_state(300, 4000) --- demo_sum +-- demo_sum -- ---------- -- 1 -- 21 @@ -899,7 +875,7 @@ SELECT demo_sum(value) OVER ( -- LOG: moving_state(0, 10000) -- LOG: moving_state(10000, 1) -- LOG: moving_state_inverse(10001, 10000) --- demo_sum +-- demo_sum -- ---------- -- 10001 -- 1 diff --git a/pgrx-examples/aggregate/src/lib.rs b/pgrx-examples/aggregate/src/lib.rs index a6487a257b..25a4e503e5 100644 --- a/pgrx-examples/aggregate/src/lib.rs +++ b/pgrx-examples/aggregate/src/lib.rs @@ -16,7 +16,8 @@ use std::str::FromStr; pgrx::pg_module_magic!(c"aggregate", pgrx::pg_sys::PG_VERSION); -#[derive(Copy, Clone, PostgresType, Serialize, Deserialize)] +#[derive(Copy, Clone, PostgresType, Serialize, Deserialize, AggregateName)] +#[aggregate_name = "DEMOAVG"] #[pgvarlena_inoutfuncs] #[derive(Default)] pub struct IntegerAvgState { @@ -27,9 +28,9 @@ pub struct IntegerAvgState { impl IntegerAvgState { #[inline(always)] fn state( - mut current: ::State, - arg: ::Args, - ) -> ::State { + mut current: >::State, + arg: >::Args, + ) -> >::State { if let Some(arg) = arg { current.sum += arg; current.n += 1; @@ -38,7 +39,7 @@ impl IntegerAvgState { } #[inline(always)] - fn finalize(current: ::State) -> ::Finalize { + fn finalize(current: >::State) -> >::Finalize { current.sum / current.n } } @@ -74,10 +75,9 @@ impl PgVarlenaInOutFuncs for IntegerAvgState { // In order to improve the testability of your code, it's encouraged to make this implementation // call to your own functions which don't require a PostgreSQL made [`pgrx::pg_sys::FunctionCallInfo`]. #[pg_aggregate] -impl Aggregate for IntegerAvgState { +impl Aggregate for IntegerAvgState { type State = PgVarlena; type Args = pgrx::name!(value, Option); - const NAME: &'static str = "DEMOAVG"; const INITIAL_CONDITION: Option<&'static str> = Some("0,0"); diff --git a/pgrx-macros/src/lib.rs b/pgrx-macros/src/lib.rs index 37c70bdfcc..6402d5932f 100644 --- a/pgrx-macros/src/lib.rs +++ b/pgrx-macros/src/lib.rs @@ -1290,6 +1290,51 @@ pub fn derive_postgres_hash(input: TokenStream) -> TokenStream { deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into() } +/// Derives the `ToAggregateName` trait. +#[proc_macro_derive(AggregateName, attributes(aggregate_name))] +pub fn derive_aggregate_name(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as syn::DeriveInput); + + impl_aggregate_name(ast).unwrap_or_else(|e| e.into_compile_error()).into() +} + +fn impl_aggregate_name(ast: DeriveInput) -> syn::Result { + let name = &ast.ident; + + let mut custom_name_value: Option = None; + + for attr in &ast.attrs { + if attr.path().is_ident("aggregate_name") { + let meta = &attr.meta; + match meta { + syn::Meta::NameValue(syn::MetaNameValue { + value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }), + .. + }) => { + custom_name_value = Some(s.value()); + break; + } + _ => { + return Err(syn::Error::new_spanned( + attr, + "#[aggregate_name] must be in the form `#[aggregate_name = \"string_literal\"]`", + )); + } + } + } + } + + let name_str = custom_name_value.unwrap_or(name.to_string()); + + let expanded = quote! { + impl ::pgrx::aggregate::ToAggregateName for #name { + const NAME: &'static str = #name_str; + } + }; + + Ok(expanded) +} + /** Declare a `pgrx::Aggregate` implementation on a type as able to used by Postgres as an aggregate. diff --git a/pgrx-sql-entity-graph/src/aggregate/mod.rs b/pgrx-sql-entity-graph/src/aggregate/mod.rs index 1578d98d1a..c6c1023409 100644 --- a/pgrx-sql-entity-graph/src/aggregate/mod.rs +++ b/pgrx-sql-entity-graph/src/aggregate/mod.rs @@ -22,6 +22,7 @@ mod options; pub use aggregate_type::{AggregateType, AggregateTypeList}; pub use options::{FinalizeModify, ParallelOption}; +use syn::PathArguments; use crate::enrich::CodeEnrichment; use crate::enrich::ToEntityGraphTokens; @@ -83,6 +84,7 @@ pub struct PgAggregate { item_impl: ItemImpl, name: Expr, target_ident: Ident, + snake_case_target_ident: Ident, pg_externs: Vec, // Note these should not be considered *writable*, they're snapshots from construction. type_args: AggregateTypeList, @@ -108,6 +110,71 @@ pub struct PgAggregate { to_sql_config: ToSqlConfig, } +fn extract_generic_from_trait(item_impl: &ItemImpl) -> Result<&Type, syn::Error> { + let (_, path, _) = item_impl.trait_.as_ref().ok_or_else(|| { + syn::Error::new_spanned( + item_impl, + "`#[pg_aggregate]` can only be used on `impl` blocks for a trait.", + ) + })?; + + let last_segment = path + .segments + .last() + .ok_or_else(|| syn::Error::new_spanned(path, "Trait path is empty or malformed."))?; + + if last_segment.ident != "Aggregate" { + return Err(syn::Error::new_spanned( + last_segment.ident.clone(), + "`#[pg_aggregate]` only works with the `Aggregate` trait.", + )); + } + + let args = match &last_segment.arguments { + PathArguments::AngleBracketed(args) => args, + _ => { + return Err(syn::Error::new_spanned( + last_segment.ident.clone(), + "`Aggregate` trait must have angle-bracketed generic arguments (e.g., `Aggregate`). Missing generic argument.", + )); + } + }; + + let generic_arg = args.args.first().ok_or_else(|| { + syn::Error::new_spanned( + args, + "`Aggregate` trait requires at least one generic argument (e.g., `Aggregate`).", + ) + })?; + + if let syn::GenericArgument::Type(ty) = generic_arg { + Ok(ty) + } else { + Err(syn::Error::new_spanned( + generic_arg, + "Expected a type as the generic argument for `Aggregate` (e.g., `Aggregate`).", + )) + } +} + +fn get_generic_type_name(ty: &syn::Type) -> Result { + if let Type::Path(type_path) = ty { + if let Some(ident) = type_path.path.segments.last().map(|s| &s.ident) { + let ident = ident.to_string(); + + match ident.as_str() { + "!" => Ok("never".to_string()), + "()" => Ok("unit".to_string()), + _ => Ok(ident), + } + } else { + Err(syn::Error::new_spanned(ty, "Generic type path is empty or malformed.")) + } + } else { + Err(syn::Error::new_spanned(ty, "Expected a path type for the generic argument.")) + } +} + impl PgAggregate { pub fn new(mut item_impl: ItemImpl) -> Result, syn::Error> { let to_sql_config = @@ -115,54 +182,21 @@ impl PgAggregate { let target_path = get_target_path(&item_impl)?; let target_ident = get_target_ident(&target_path)?; - let snake_case_target_ident = - Ident::new(&target_ident.to_string().to_case(Case::Snake), target_ident.span()); - crate::ident_is_acceptable_to_postgres(&snake_case_target_ident)?; - let mut pg_externs = Vec::default(); // We want to avoid having multiple borrows, so we take a snapshot to scan from, // and mutate the actual one. let item_impl_snapshot = item_impl.clone(); - if let Some((_, ref path, _)) = item_impl.trait_ { - // TODO: Consider checking the path if there is more than one segment to make sure it's pgrx. - if let Some(last) = path.segments.last() { - if last.ident != "Aggregate" { - return Err(syn::Error::new( - last.ident.span(), - "`#[pg_aggregate]` only works with the `Aggregate` trait.", - )); - } - } - } + let generic_type = extract_generic_from_trait(&item_impl)?.clone(); + let generic_type_name = get_generic_type_name(&generic_type)?; - let name = match get_impl_const_by_name(&item_impl_snapshot, "NAME") { - Some(item_const) => match &item_const.expr { - syn::Expr::Lit(ref expr) => { - if let syn::Lit::Str(_) = &expr.lit { - item_const.expr.clone() - } else { - return Err(syn::Error::new( - expr.span(), - "`NAME` must be a `&'static str` for Aggregate implementations.", - )); - } - } - e => { - return Err(syn::Error::new( - e.span(), - "`NAME` must be a `&'static str` for Aggregate implementations.", - )); - } - }, - None => { - item_impl.items.push(parse_quote! { - const NAME: &'static str = stringify!(Self); - }); - parse_quote! { - stringify!(#target_ident) - } - } + let snake_case_target_ident = + format!("{}_{}", target_ident, generic_type_name).to_case(Case::Snake); + let snake_case_target_ident = Ident::new(&snake_case_target_ident, target_ident.span()); + crate::ident_is_acceptable_to_postgres(&snake_case_target_ident)?; + + let name = parse_quote! { + <#generic_type as ::pgrx::aggregate::ToAggregateName>::NAME }; // `State` is an optional value, we default to `Self`. @@ -301,9 +335,9 @@ impl PgAggregate { #pg_extern_attr fn #fn_name(this: #type_state_without_self, #(#args_with_names),*, fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> #type_state_without_self { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::state(this, (#(#arg_names),*), fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::state(this, (#(#arg_names),*), fcinfo) ) } } @@ -326,9 +360,9 @@ impl PgAggregate { #pg_extern_attr fn #fn_name(this: #type_state_without_self, v: #type_state_without_self, fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> #type_state_without_self { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::combine(this, v, fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::combine(this, v, fcinfo) ) } } @@ -357,9 +391,9 @@ impl PgAggregate { #pg_extern_attr fn #fn_name(this: #type_state_without_self, #(#direct_args_with_names),*, fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> #type_finalize { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::finalize(this, (#(#direct_arg_names),*), fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::finalize(this, (#(#direct_arg_names),*), fcinfo) ) } } @@ -370,9 +404,9 @@ impl PgAggregate { #pg_extern_attr fn #fn_name(this: #type_state_without_self, fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> #type_finalize { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::finalize(this, (), fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::finalize(this, (), fcinfo) ) } } @@ -398,9 +432,9 @@ impl PgAggregate { #pg_extern_attr fn #fn_name(this: #type_state_without_self, fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> Vec { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::serial(this, fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::serial(this, fcinfo) ) } } @@ -427,9 +461,9 @@ impl PgAggregate { #pg_extern_attr fn #fn_name(this: #type_state_without_self, buf: Vec, internal: ::pgrx::pgbox::PgBox<#type_state_without_self>, fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> ::pgrx::pgbox::PgBox<#type_state_without_self> { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::deserial(this, buf, internal, fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::deserial(this, buf, internal, fcinfo) ) } } @@ -461,9 +495,9 @@ impl PgAggregate { fcinfo: ::pgrx::pg_sys::FunctionCallInfo, ) -> #type_moving_state { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::moving_state(mstate, (#(#arg_names),*), fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::moving_state(mstate, (#(#arg_names),*), fcinfo) ) } } @@ -472,10 +506,10 @@ impl PgAggregate { } else { item_impl.items.push(parse_quote! { fn moving_state( - _mstate: <#target_path as ::pgrx::aggregate::Aggregate>::MovingState, + _mstate: <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::MovingState, _v: Self::Args, _fcinfo: ::pgrx::pg_sys::FunctionCallInfo, - ) -> <#target_path as ::pgrx::aggregate::Aggregate>::MovingState { + ) -> <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::MovingState { unimplemented!("Call to moving_state on an aggregate which does not support it.") } }); @@ -499,9 +533,9 @@ impl PgAggregate { fcinfo: ::pgrx::pg_sys::FunctionCallInfo, ) -> #type_moving_state { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::moving_state_inverse(mstate, (#(#arg_names),*), fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::moving_state_inverse(mstate, (#(#arg_names),*), fcinfo) ) } } @@ -535,9 +569,9 @@ impl PgAggregate { #pg_extern_attr fn #fn_name(mstate: #type_moving_state, #(#direct_args_with_names),* #maybe_comma fcinfo: ::pgrx::pg_sys::FunctionCallInfo) -> #type_finalize { unsafe { - <#target_path as ::pgrx::aggregate::Aggregate>::in_memory_context( + <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::in_memory_context( fcinfo, - move |_context| <#target_path as ::pgrx::aggregate::Aggregate>::moving_finalize(mstate, (#(#direct_arg_names),*), fcinfo) + move |_context| <#target_path as ::pgrx::aggregate::Aggregate::<#generic_type>>::moving_finalize(mstate, (#(#direct_arg_names),*), fcinfo) ) } } @@ -557,6 +591,7 @@ impl PgAggregate { target_ident, pg_externs, name, + snake_case_target_ident, type_args: type_args_value, type_ordered_set_args: type_ordered_set_args_value, type_moving_state: type_moving_state_value, @@ -617,10 +652,8 @@ impl PgAggregate { impl ToEntityGraphTokens for PgAggregate { fn to_entity_graph_tokens(&self) -> TokenStream2 { let target_ident = &self.target_ident; - let snake_case_target_ident = - Ident::new(&target_ident.to_string().to_case(Case::Snake), target_ident.span()); let sql_graph_entity_fn_name = syn::Ident::new( - &format!("__pgrx_internals_aggregate_{}", snake_case_target_ident), + &format!("__pgrx_internals_aggregate_{}", self.snake_case_target_ident), target_ident.span(), ); @@ -694,6 +727,7 @@ impl ToRustCodeTokens for PgAggregate { fn to_rust_code_tokens(&self) -> TokenStream2 { let impl_item = &self.item_impl; let pg_externs = self.pg_externs.iter(); + quote! { #impl_item #(#pg_externs)* @@ -927,10 +961,9 @@ mod tests { fn agg_required_only() -> Result<()> { let tokens: ItemImpl = parse_quote! { #[pg_aggregate] - impl Aggregate for DemoAgg { + impl Aggregate for DemoAgg { type State = PgVarlena; type Args = i32; - const NAME: &'static str = "DEMO"; fn state(mut current: Self::State, arg: Self::Args) -> Self::State { todo!() @@ -945,7 +978,7 @@ mod tests { assert_eq!(agg.0.pg_externs.len(), 1); // That extern should be named specifically: let extern_fn = &agg.0.pg_externs[0]; - assert_eq!(extern_fn.sig.ident.to_string(), "demo_agg_state"); + assert_eq!(extern_fn.sig.ident.to_string(), "demo_agg_demo_name_state"); // It should be possible to generate entity tokens. let _ = agg.to_token_stream(); Ok(()) @@ -955,14 +988,12 @@ mod tests { fn agg_all_options() -> Result<()> { let tokens: ItemImpl = parse_quote! { #[pg_aggregate] - impl Aggregate for DemoAgg { + impl Aggregate for DemoAgg { type State = PgVarlena; type Args = i32; type OrderBy = i32; type MovingState = i32; - const NAME: &'static str = "DEMO"; - const PARALLEL: Option = Some(ParallelOption::Safe); const FINALIZE_MODIFY: Option = Some(FinalizeModify::ReadWrite); const MOVING_FINALIZE_MODIFY: Option = Some(FinalizeModify::ReadWrite); @@ -1011,7 +1042,7 @@ mod tests { assert_eq!(agg.0.pg_externs.len(), 8); // That extern should be named specifically: let extern_fn = &agg.0.pg_externs[0]; - assert_eq!(extern_fn.sig.ident.to_string(), "demo_agg_state"); + assert_eq!(extern_fn.sig.ident.to_string(), "demo_agg_demo_name_state"); // It should be possible to generate entity tokens. let _ = agg.to_token_stream(); Ok(()) diff --git a/pgrx-tests/src/tests/aggregate_tests.rs b/pgrx-tests/src/tests/aggregate_tests.rs index 46bfacfde5..95b1030102 100644 --- a/pgrx-tests/src/tests/aggregate_tests.rs +++ b/pgrx-tests/src/tests/aggregate_tests.rs @@ -7,19 +7,28 @@ //LICENSE All rights reserved. //LICENSE //LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file. -use pgrx::datum::Internal; use pgrx::prelude::*; +use pgrx::{datum::Internal, ToAggregateName}; use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Default, Debug, PostgresType, Serialize, Deserialize)] #[pg_binary_protocol] -pub struct DemoSum { +pub struct DemoOps { count: i32, } -#[pg_aggregate] -impl Aggregate for DemoSum { +struct DemoSumName; + +#[derive(AggregateName)] +#[aggregate_name = "demo_sub"] +struct DemoSubName; + +impl ToAggregateName for DemoSumName { const NAME: &'static str = "demo_sum"; +} + +#[pg_aggregate] +impl Aggregate for DemoOps { const PARALLEL: Option = Some(pgrx::aggregate::ParallelOption::Unsafe); const INITIAL_CONDITION: Option<&'static str> = Some(r#"0"#); const MOVING_INITIAL_CONDITION: Option<&'static str> = Some(r#"0"#); @@ -42,7 +51,7 @@ impl Aggregate for DemoSum { arg: Self::Args, fcinfo: pg_sys::FunctionCallInfo, ) -> Self::MovingState { - Self::state(current, arg, fcinfo) + >::state(current, arg, fcinfo) } fn moving_state_inverse( @@ -64,12 +73,58 @@ impl Aggregate for DemoSum { } } -#[derive(Copy, Clone, Default, Debug, PostgresType, Serialize, Deserialize)] +#[pg_aggregate] +impl Aggregate for DemoOps { + const PARALLEL: Option = Some(pgrx::aggregate::ParallelOption::Unsafe); + const INITIAL_CONDITION: Option<&'static str> = Some(r#"0"#); + const MOVING_INITIAL_CONDITION: Option<&'static str> = Some(r#"0"#); + + type Args = i32; + type State = i32; + type MovingState = i32; + + fn state( + mut current: Self::State, + arg: Self::Args, + _fcinfo: pg_sys::FunctionCallInfo, + ) -> Self::State { + current -= arg; + current + } + + fn moving_state( + current: Self::State, + arg: Self::Args, + fcinfo: pg_sys::FunctionCallInfo, + ) -> Self::MovingState { + >::state(current, arg, fcinfo) + } + + fn moving_state_inverse( + mut current: Self::State, + arg: Self::Args, + _fcinfo: pg_sys::FunctionCallInfo, + ) -> Self::MovingState { + current += arg; + current + } + + fn combine( + mut first: Self::State, + second: Self::State, + _fcinfo: pg_sys::FunctionCallInfo, + ) -> Self::State { + first -= second; + first + } +} + +#[derive(Copy, Clone, Default, Debug, PostgresType, Serialize, Deserialize, AggregateName)] #[pg_binary_protocol] pub struct DemoPercentileDisc; #[pg_aggregate] -impl Aggregate for DemoPercentileDisc { +impl Aggregate for DemoPercentileDisc { type Args = name!(input, i32); type State = Internal; type Finalize = i32; @@ -116,10 +171,14 @@ mod demo_schema { #[pg_binary_protocol] pub struct DemoCustomState; +impl ToAggregateName for DemoCustomState { + const NAME: &'static str = "demo_sum_state"; +} + // demonstrate we can properly support an STYPE with a pg_schema #[pg_aggregate] -impl Aggregate for DemoCustomState { - const NAME: &'static str = "demo_sum_state"; +impl Aggregate for DemoCustomState { + //const NAME: &'static str = "demo_sum_state"; type Args = i32; type State = Option; type Finalize = i32; @@ -147,10 +206,11 @@ impl Aggregate for DemoCustomState { } } +#[derive(AggregateName)] struct FirstJson; #[pg_aggregate] -impl Aggregate for FirstJson { +impl Aggregate for FirstJson { type State = pgrx::Json; type Args = pgrx::name!(value, pgrx::Json); @@ -164,10 +224,11 @@ impl Aggregate for FirstJson { } } +#[derive(AggregateName)] struct FirstJsonB; #[pg_aggregate] -impl Aggregate for FirstJsonB { +impl Aggregate for FirstJsonB { type State = pgrx::JsonB; type Args = pgrx::name!(value, pgrx::JsonB); @@ -181,10 +242,11 @@ impl Aggregate for FirstJsonB { } } +#[derive(AggregateName)] struct FirstAnyArray; #[pg_aggregate] -impl Aggregate for FirstAnyArray { +impl Aggregate for FirstAnyArray { type State = pgrx::AnyArray; type Args = pgrx::name!(value, pgrx::AnyArray); @@ -198,10 +260,11 @@ impl Aggregate for FirstAnyArray { } } +#[derive(AggregateName)] struct FirstAnyElement; #[pg_aggregate] -impl Aggregate for FirstAnyElement { +impl Aggregate for FirstAnyElement { type State = pgrx::AnyElement; type Args = pgrx::name!(value, pgrx::AnyElement); @@ -241,6 +304,25 @@ mod tests { assert_eq!(retval, Ok(Some(vec![1, 21, 320, 4300]))); } + #[pg_test] + fn aggregate_demo_sub() { + let retval = + Spi::get_one::("SELECT demo_sub(value) FROM UNNEST(ARRAY [1, 1, 2]) as value;"); + assert_eq!(retval, Ok(Some(-4))); + + // Moving-aggregate mode + let retval = Spi::get_one::>( + " + SELECT array_agg(calculated) FROM ( + SELECT demo_sub(value) OVER ( + ROWS BETWEEN 1 PRECEDING AND CURRENT ROW + ) as calculated FROM UNNEST(ARRAY [1, 20, 300, 4000]) as value + ) as results; + ", + ); + assert_eq!(retval, Ok(Some(vec![-1, -21, -320, -4300]))); + } + #[pg_test] fn aggregate_demo_percentile_disc() { // Example from https://www.postgresql.org/docs/current/xaggr.html#XAGGR-ORDERED-SET-AGGREGATES diff --git a/pgrx-tests/src/tests/issue1134.rs b/pgrx-tests/src/tests/issue1134.rs index 1d100d658e..95bdd05d23 100644 --- a/pgrx-tests/src/tests/issue1134.rs +++ b/pgrx-tests/src/tests/issue1134.rs @@ -10,11 +10,11 @@ // If this code doesn't generate a syntax error in the generated SQL then PR #1134 is working as expected use pgrx::{prelude::*, Internal}; +#[derive(AggregateName)] pub struct Foo; #[pg_aggregate] -impl Aggregate for Foo { - const NAME: &'static str = "foo"; +impl Aggregate for Foo { const ORDERED_SET: bool = true; type OrderedSetArgs = (name!(a, f64), name!(b, f64)); diff --git a/pgrx-tests/src/tests/pgrx_module_qualification.rs b/pgrx-tests/src/tests/pgrx_module_qualification.rs index e7b2a50d04..9d03aa3335 100644 --- a/pgrx-tests/src/tests/pgrx_module_qualification.rs +++ b/pgrx-tests/src/tests/pgrx_module_qualification.rs @@ -30,7 +30,7 @@ mod pgrx_modqual_tests { use pgrx_macros::{ opname, pg_aggregate, pg_extern, pg_guard, pg_operator, pg_schema, pg_trigger, pgrx, - PostgresEq, PostgresHash, PostgresOrd, PostgresType, + AggregateName, PostgresEq, PostgresHash, PostgresOrd, PostgresType, }; ::pgrx::extension_sql!("SELECT 1;", name = "pgrx_module_qualification_test"); @@ -55,6 +55,10 @@ mod pgrx_modqual_tests { v: i32, } + #[derive(AggregateName)] + #[aggregate_name = "PgrxModuleQualificationTestAgg"] + struct PgrxMQTName; + #[pg_extern] fn foo() {} @@ -136,10 +140,9 @@ mod pgrx_modqual_tests { } #[pg_aggregate] - impl ::pgrx::Aggregate for PgrxModuleQualificationTest { + impl ::pgrx::Aggregate for PgrxModuleQualificationTest { type State = ::pgrx::datum::PgVarlena; type Args = ::pgrx::name!(value, Option); - const NAME: &'static str = "PgrxModuleQualificationTestAgg"; const INITIAL_CONDITION: Option<&'static str> = Some(r#"{"v": 0}"#); diff --git a/pgrx-tests/tests/compile-fail/aggregate-functions-dont-run-forever.rs b/pgrx-tests/tests/compile-fail/aggregate-functions-dont-run-forever.rs index 6cb35a6e5e..5240ed0c0a 100644 --- a/pgrx-tests/tests/compile-fail/aggregate-functions-dont-run-forever.rs +++ b/pgrx-tests/tests/compile-fail/aggregate-functions-dont-run-forever.rs @@ -5,10 +5,11 @@ use std::collections::HashSet; const DOG_COMPOSITE_TYPE: &str = "Dog"; +#[derive(pgrx::AggregateName)] struct SumScritches {} #[pg_aggregate] -impl Aggregate for SumScritches { +impl Aggregate for SumScritches { type State = i32; const INITIAL_CONDITION: Option<&'static str> = Some("0"); type Args = pgrx::name!(value, pgrx::composite_type!('static, "Dog")); @@ -42,10 +43,11 @@ CREATE AGGREGATE scritch_collector ("value" integer) ( ) ``` */ +#[derive(pgrx::AggregateName)] struct ScritchCollector; #[pg_aggregate] -impl Aggregate for ScritchCollector { +impl Aggregate for ScritchCollector { type State = Option; type Args = i32; @@ -64,11 +66,11 @@ impl Aggregate for ScritchCollector { } } -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Default, Debug, pgrx::AggregateName)] pub struct DemoUnique; #[pg_aggregate] -impl Aggregate for DemoUnique { +impl Aggregate for DemoUnique { type Args = &'static str; type State = Internal; type Finalize = i32; @@ -110,8 +112,12 @@ impl Aggregate for DemoUnique { #[derive(Copy, Clone, Default, Debug)] pub struct AggregateWithOrderedSetArgs; +#[derive(pgrx::AggregateName)] +#[aggregate_name = "AggregateWithOrderedSetArgs"] +struct AggWOS; + #[pg_aggregate] -impl Aggregate for AggregateWithOrderedSetArgs { +impl Aggregate for AggregateWithOrderedSetArgs { type Args = name!(input, pgrx::composite_type!('static, "Dog")); type State = pgrx::composite_type!('static, "Dog"); type Finalize = pgrx::composite_type!('static, "Dog"); @@ -138,8 +144,12 @@ impl Aggregate for AggregateWithOrderedSetArgs { #[derive(Copy, Clone, Default, Debug)] pub struct AggregateWithMovingState; +#[derive(pgrx::AggregateName)] +#[aggregate_name = "AggregateWithMovingState"] +struct AggWMS; + #[pg_aggregate] -impl Aggregate for AggregateWithMovingState { +impl Aggregate for AggregateWithMovingState { type Args = pgrx::composite_type!('static, "Dog"); type State = pgrx::composite_type!('static, "Dog"); type MovingState = pgrx::composite_type!('static, "Dog"); diff --git a/pgrx-tests/tests/compile-fail/aggregate-functions-dont-run-forever.stderr b/pgrx-tests/tests/compile-fail/aggregate-functions-dont-run-forever.stderr index 3d002ebdca..657573d69b 100644 --- a/pgrx-tests/tests/compile-fail/aggregate-functions-dont-run-forever.stderr +++ b/pgrx-tests/tests/compile-fail/aggregate-functions-dont-run-forever.stderr @@ -1,7 +1,7 @@ error[E0521]: borrowed data escapes outside of function - --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:10:1 + --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:11:1 | -10 | #[pg_aggregate] +11 | #[pg_aggregate] | ^^^^^^^^^^^^^^^ | | | `fcinfo` is a reference that is only valid in the function body @@ -12,9 +12,9 @@ error[E0521]: borrowed data escapes outside of function = note: this error originates in the attribute macro `::pgrx::pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0521]: borrowed data escapes outside of function - --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:47:1 + --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:49:1 | -47 | #[pg_aggregate] +49 | #[pg_aggregate] | ^^^^^^^^^^^^^^^ | | | `fcinfo` is a reference that is only valid in the function body @@ -25,9 +25,9 @@ error[E0521]: borrowed data escapes outside of function = note: this error originates in the attribute macro `::pgrx::pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0521]: borrowed data escapes outside of function - --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:70:1 + --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:72:1 | -70 | #[pg_aggregate] +72 | #[pg_aggregate] | ^^^^^^^^^^^^^^^ | | | `fcinfo` is a reference that is only valid in the function body @@ -38,9 +38,9 @@ error[E0521]: borrowed data escapes outside of function = note: this error originates in the attribute macro `::pgrx::pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0521]: borrowed data escapes outside of function - --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:113:1 + --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:119:1 | -113 | #[pg_aggregate] +119 | #[pg_aggregate] | ^^^^^^^^^^^^^^^ | | | `fcinfo` is a reference that is only valid in the function body @@ -51,9 +51,9 @@ error[E0521]: borrowed data escapes outside of function = note: this error originates in the attribute macro `::pgrx::pg_extern` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0521]: borrowed data escapes outside of function - --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:141:1 + --> tests/compile-fail/aggregate-functions-dont-run-forever.rs:151:1 | -141 | #[pg_aggregate] +151 | #[pg_aggregate] | ^^^^^^^^^^^^^^^ | | | `fcinfo` is a reference that is only valid in the function body diff --git a/pgrx/src/aggregate.rs b/pgrx/src/aggregate.rs index 31d5a455bf..24694a1ce0 100644 --- a/pgrx/src/aggregate.rs +++ b/pgrx/src/aggregate.rs @@ -28,14 +28,14 @@ use serde::{Serialize, Deserialize}; // pg_module_magic!(); // Uncomment this outside of docs! -#[derive(Copy, Clone, Default, PostgresType, Serialize, Deserialize)] +#[derive(Copy, Clone, Default, PostgresType, Serialize, Deserialize, AggregateName)] #[pg_binary_protocol] pub struct DemoSum { count: i32, } #[pg_aggregate] -impl Aggregate for DemoSum { +impl Aggregate for DemoSum { const INITIAL_CONDITION: Option<&'static str> = Some(r#"{ "count": 0 }"#); type Args = i32; fn state( @@ -87,13 +87,13 @@ Sometimes aggregates need to handle multiple arguments. The # use pgrx::prelude::*; # use serde::{Serialize, Deserialize}; # -# #[derive(Copy, Clone, Default, PostgresType, Serialize, Deserialize)] +# #[derive(Copy, Clone, Default, PostgresType, Serialize, Deserialize, AggregateName)] # pub struct DemoSum { # count: i32, # } # #[pg_aggregate] -impl Aggregate for DemoSum { +impl Aggregate for DemoSum { const INITIAL_CONDITION: Option<&'static str> = Some(r#"{ "count": 0 }"#); type Args = (i32, i32); fn state( @@ -132,13 +132,13 @@ The [`name!(ident, Type)`][macro@crate::name] macro can be used to set the name # use pgrx::prelude::*; # use serde::{Serialize, Deserialize}; # -# #[derive(Copy, Clone, Default, PostgresType, Serialize, Deserialize)] +# #[derive(Copy, Clone, Default, PostgresType, Serialize, Deserialize, AggregateName)] # pub struct DemoSum { # count: i32, # } # # #[pg_aggregate] -impl Aggregate for DemoSum { +impl Aggregate for DemoSum { const INITIAL_CONDITION: Option<&'static str> = Some(r#"{ "count": 0 }"#); type Args = ( i32, @@ -179,13 +179,13 @@ accepts the same parameters as [`#[pg_extern]`][macro@pgrx-macros::pg_extern]. # use pgrx::prelude::*; # use serde::{Serialize, Deserialize}; # -# #[derive(Copy, Clone, Default, PostgresType, Serialize, Deserialize)] +# #[derive(Copy, Clone, Default, PostgresType, Serialize, Deserialize, AggregateName)] # pub struct DemoSum { # count: i32, # } # #[pg_aggregate] -impl Aggregate for DemoSum { +impl Aggregate for DemoSum { const INITIAL_CONDITION: Option<&'static str> = Some(r#"{ "count": 0 }"#); type Args = i32; #[pgrx(parallel_safe, immutable)] @@ -227,10 +227,11 @@ pub struct DemoSumState { count: i32, } +#[derive(AggregateName)] pub struct DemoSum; #[pg_aggregate] -impl Aggregate for DemoSum { +impl Aggregate for DemoSum { const INITIAL_CONDITION: Option<&'static str> = Some(r#"{ "count": 0 }"#); type Args = i32; type State = DemoSumState; @@ -278,6 +279,39 @@ use crate::pgbox::PgBox; pub use pgrx_sql_entity_graph::{FinalizeModify, ParallelOption}; +/// A trait representing a type-level identifier for an aggregate function. +/// +/// This trait is used to associate a constant name with a Rust type, which is then used +/// as the name of the corresponding SQL aggregate in PostgreSQL (e.g., the name you'd use in +/// `SELECT my_agg(col) FROM table;`). +/// +/// This is especially useful in combination with procedural macros like +/// [`#[derive(AggregateName)]`](macro@crate::AggregateName), which automatically implements this trait +/// for a given struct or enum. +/// +/// # Examples +/// +/// ## Default name (based on type name) +/// ``` +/// #[derive(pgrx::AggregateName)] +/// struct MySum; +/// +/// assert_eq!(::NAME, "MySum"); +/// ``` +/// +/// ## Custom name using `#[aggregate_name = "..."]` +/// ``` +/// #[derive(pgrx::AggregateName)] +/// #[aggregate_name = "my_custom_sum"] +/// struct MySum; +/// +/// assert_eq!(::NAME, "my_custom_sum"); +/// ``` +pub trait ToAggregateName { + /// The name of the aggregate. (eg. What you'd pass to `SELECT agg(col) FROM tab`.) + const NAME: &'static str; +} + /// Aggregate implementation trait. /// /// When decorated with [`#[pgrx_macros::pg_aggregate]`](pgrx_macros::pg_aggregate), enables the @@ -286,7 +320,7 @@ pub use pgrx_sql_entity_graph::{FinalizeModify, ParallelOption}; /// /// The [`#[pgrx_macros::pg_aggregate]`](pgrx_macros::pg_aggregate) will automatically fill fields /// marked optional with stubs. -pub trait Aggregate +pub trait Aggregate where Self: Sized, { @@ -336,9 +370,6 @@ where /// **Optional:** This function can be skipped, `#[pg_aggregate]` will create a stub. type MovingState; - /// The name of the aggregate. (eg. What you'd pass to `SELECT agg(col) FROM tab`.) - const NAME: &'static str; - /// Set to true if this is an ordered set aggregate. /// /// If set, the `OrderedSetArgs` associated type becomes effective, this allows for