Skip to content

Commit 7b8abe6

Browse files
if0nedaamien
authored andcommitted
Allows to create multiple aggregates for the same Rust type (pgcentralfoundation#2078)
Closes pgcentralfoundation#2077 Unfortunately, the rust doesn't allow using `&'static str` in const generic yet. This would allow simply moving an associative constant to a generic trait. Therefore an auxiliary trait `ToAggregateName` with associative constant `NAME` was added. This allows `Aggregate` to be implemented multiple times for the same type with different names. This change introduces a bit of boilerplate. In order to simplify the use of this trait, a derive macro `AggregateName` has been developed, which automatically implements this trait. In this case, `NAME` becomes equal to the name of the structure for which this macro is used. To change the name of the name you should use attribute `#[aggregate_name = “custom_name”]`. Example: ```rust #[derive(Copy, Clone, Default, Debug, PostgresType, Serialize, Deserialize)] pub struct DemoOps { count: i32, } struct DemoSumName; // Using derive macro #[derive(AggregateName)] #[aggregate_name = "demo_sub"] struct DemoSubName; // Explicit trait implementation impl ToAggregateName for DemoSumName { const NAME: &'static str = "demo_sum"; } // The first aggregate: #[pg_aggregate] impl Aggregate<DemoSumName> for DemoOps { /* implementation */ } // The second aggregate: #[pg_aggregate] impl Aggregate<DemoSubName> for DemoOps { /* implementation */ } ``` **BREAKING CHANGES**: Now for the `Aggregate` trait you need to specify a generic parameter that implements the `ToAggregateName` trait. I tried to leave backwards compatibility, but decided to break it after all so that developers using `pgrx` would see that the semantics of the `Aggregate` trait had changed.
1 parent 5fa6fa7 commit 7b8abe6

10 files changed

Lines changed: 363 additions & 185 deletions

File tree

articles/postgresql-aggregates-with-rust.md

Lines changed: 35 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ CREATE AGGREGATE example_sum(integer)
4747
);
4848

4949
SELECT example_sum(value) FROM UNNEST(ARRAY [1, 2, 3]) as value;
50-
-- example_sum
50+
-- example_sum
5151
-- -------------
5252
-- 6
5353
-- (1 row)
@@ -91,7 +91,7 @@ CREATE AGGREGATE example_sum(integer)
9191
);
9292

9393
SELECT example_sum(value) FROM generate_series(0, 4000) as value;
94-
-- example_sum
94+
-- example_sum
9595
-- -------------
9696
-- 8002000
9797
-- (1 row)
@@ -128,7 +128,7 @@ CREATE AGGREGATE example_uniq(text)
128128
);
129129

130130
SELECT example_uniq(value) FROM UNNEST(ARRAY ['a', 'a', 'b']) as value;
131-
-- example_uniq
131+
-- example_uniq
132132
-- --------------
133133
-- 2
134134
-- (1 row)
@@ -162,7 +162,7 @@ SELECT example_concat(first, second, third) FROM
162162
UNNEST(ARRAY ['a', 'b', 'c']) as first,
163163
UNNEST(ARRAY ['1', '2', '3']) as second,
164164
UNNEST(ARRAY ['!', '@', '#']) as third;
165-
-- example_concat
165+
-- example_concat
166166
-- ---------------------------------------------------------------------------------------------------------------
167167
-- {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#}
168168
-- (1 row)
@@ -174,7 +174,7 @@ See how we see `a1`, `b1`, and `c1`? Multiple arguments might not work as you ex
174174
SELECT UNNEST(ARRAY ['a', 'b', 'c']) as first,
175175
UNNEST(ARRAY ['1', '2', '3']) as second,
176176
UNNEST(ARRAY ['!', '@', '#']) as third;
177-
-- first | second | third
177+
-- first | second | third
178178
-- -------+--------+-------
179179
-- a | 1 | !
180180
-- b | 2 | @
@@ -202,7 +202,7 @@ It includes:
202202

203203
If a Rust toolchain is not already installed, please follow the instructions on [rustup.rs][rustup-rs].
204204

205-
You'll also [need to make sure you have some development libraries][pgrx-system-requirements] like `zlib` and `libclang`, as
205+
You'll also [need to make sure you have some development libraries][pgrx-system-requirements] like `zlib` and `libclang`, as
206206
`cargo pgrx init` will, by default, build it's own development PostgreSQL installs. Usually it's possible to
207207
figure out if something is missing from error messages and then discover the required package for the system.
208208

@@ -244,7 +244,7 @@ running SQL generator
244244
psql (13.5)
245245
Type "help" for help.
246246

247-
exploring_aggregates=#
247+
exploring_aggregates=#
248248
```
249249

250250
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;
268268

269269
\dx+ exploring_aggregates
270270
-- Objects in extension "exploring_aggregates"
271-
-- Object description
271+
-- Object description
272272
-- ---------------------------------------
273273
-- function hello_exploring_aggregates()
274274
-- (1 row)
275275

276276
SELECT hello_exploring_aggregates();
277-
-- hello_exploring_aggregates
277+
-- hello_exploring_aggregates
278278
-- -----------------------------
279279
-- Hello, exploring_aggregates
280280
-- (1 row)
@@ -337,7 +337,7 @@ running SQL generator
337337
This creates `sql/exploring_aggregates-0.0.0.sql`:
338338

339339
```sql
340-
/*
340+
/*
341341
This file is auto generated by pgrx.
342342
343343
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.
378378
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]
379379
procedural macro on an [`pgrx::Aggregate`][pgrx-aggregate-aggregate] implementation for that type.
380380

381+
The aggregate name is specified through a type parameter that implements the `ToAggregateName` trait.
382+
Or you can use a derive-macro `AggregateName` to automatically implement this trait for some type.
383+
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
384+
381385
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
382386
stubs for all non-essential items. The state type (the implementation target by default) must have a [`#[derive(PostgresType)]`][pgrx-postgrestype] declaration,
383387
or be a type PostgreSQL already knows about.
@@ -390,13 +394,14 @@ use serde::{Serialize, Deserialize};
390394

391395
pg_module_magic!();
392396

393-
#[derive(Copy, Clone, Default, Debug, PostgresType, Serialize, Deserialize)]
397+
#[derive(Copy, Clone, Default, Debug, PostgresType, Serialize, Deserialize, AggregateName)]
398+
#[aggregate_name = "DemoSum"]
394399
pub struct DemoSum {
395400
count: i32,
396401
}
397402

398403
#[pg_aggregate]
399-
impl Aggregate for DemoSum {
404+
impl Aggregate<DemoSum> for DemoSum {
400405
const INITIAL_CONDITION: Option<&'static str> = Some(r#"{ "count": 0 }"#);
401406
type Args = i32;
402407
fn state(
@@ -413,7 +418,7 @@ impl Aggregate for DemoSum {
413418
We can review the generated SQL (generated via `cargo pgrx schema`):
414419

415420
```sql
416-
/*
421+
/*
417422
This file is auto generated by pgrx.
418423
419424
The ordering of items is not stable, it is driven by a dependency graph.
@@ -495,7 +500,7 @@ running SQL generator
495500
psql (13.5)
496501
Type "help" for help.
497502

498-
exploring_aggregates=#
503+
exploring_aggregates=#
499504
```
500505

501506
Now we're connected via `psql`:
@@ -505,7 +510,7 @@ CREATE EXTENSION exploring_aggregates;
505510
-- CREATE EXTENSION
506511

507512
SELECT DemoSum(value) FROM generate_series(0, 4000) as value;
508-
-- demosum
513+
-- demosum
509514
-- -------------------
510515
-- {"count":8002000}
511516
-- (1 row)
@@ -518,11 +523,11 @@ Pretty cool!
518523
Let's change the [`State`][pgrx-aggregate-aggregate-state] this time:
519524

520525
```rust
521-
#[derive(Copy, Clone, Default, Debug)]
526+
#[derive(Copy, Clone, Default, Debug, AggregateName)]
522527
pub struct DemoSum;
523528

524529
#[pg_aggregate]
525-
impl Aggregate for DemoSum {
530+
impl Aggregate<DemoSum> for DemoSum {
526531
const INITIAL_CONDITION: Option<&'static str> = Some(r#"0"#);
527532
type Args = i32;
528533
type State = i32;
@@ -542,7 +547,7 @@ Now when we run it:
542547

543548
```sql
544549
SELECT DemoSum(value) FROM generate_series(0, 4000) as value;
545-
-- demosum
550+
-- demosum
546551
-- ---------
547552
-- 8002000
548553
-- (1 row)
@@ -552,7 +557,7 @@ This is a fine reimplementation of `SUM` so far, but as we saw previously we nee
552557

553558
```rust
554559
#[pg_aggregate]
555-
impl Aggregate for DemoSum {
560+
impl Aggregate<DemoSum> for DemoSum {
556561
// ...
557562
fn combine(
558563
mut first: Self::State,
@@ -565,35 +570,6 @@ impl Aggregate for DemoSum {
565570
}
566571
```
567572

568-
We can also change the name of the generated aggregate, or set the [`PARALLEL`][pgrx-aggregate-aggregate-parallel] settings, for example:
569-
570-
```rust
571-
#[pg_aggregate]
572-
impl Aggregate for DemoSum {
573-
// ...
574-
const NAME: &'static str = "demo_sum";
575-
const PARALLEL: Option<ParallelOption> = Some(pgrx::aggregate::ParallelOption::Unsafe);
576-
// ...
577-
}
578-
```
579-
580-
This generates:
581-
582-
```sql
583-
-- src/lib.rs:9
584-
-- exploring_aggregates::DemoSum
585-
CREATE AGGREGATE demo_sum (
586-
integer /* i32 */
587-
)
588-
(
589-
SFUNC = "demo_sum_state", /* exploring_aggregates::DemoSum::state */
590-
STYPE = integer, /* i32 */
591-
COMBINEFUNC = "demo_sum_combine", /* exploring_aggregates::DemoSum::combine */
592-
INITCOND = '0', /* exploring_aggregates::DemoSum::INITIAL_CONDITION */
593-
PARALLEL = UNSAFE /* exploring_aggregates::DemoSum::PARALLEL */
594-
);
595-
```
596-
597573
## Rust state types
598574

599575
It's possible to use a non-SQL (say, [`HashSet<String>`][std::collections::HashSet]) type as a state by using [`Internal`][pgrx::datum::Internal].
@@ -608,11 +584,11 @@ use std::collections::HashSet;
608584

609585
pg_module_magic!();
610586

611-
#[derive(Copy, Clone, Default, Debug)]
587+
#[derive(Copy, Clone, Default, Debug, AggregateName)]
612588
pub struct DemoUnique;
613589

614590
#[pg_aggregate]
615-
impl Aggregate for DemoUnique {
591+
impl Aggregate<DemoUnique> for DemoUnique {
616592
type Args = &'static str;
617593
type State = Internal;
618594
type Finalize = i32;
@@ -656,7 +632,7 @@ We can test it:
656632

657633
```sql
658634
SELECT DemoUnique(value) FROM UNNEST(ARRAY ['a', 'a', 'b']) as value;
659-
-- demounique
635+
-- demounique
660636
-- ------------
661637
-- 2
662638
-- (1 row)
@@ -674,11 +650,11 @@ PostgreSQL also supports what are called [*Ordered-Set Aggregates*][postgresql-o
674650
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.
675651

676652
```rust
677-
#[derive(Copy, Clone, Default, Debug)]
653+
#[derive(Copy, Clone, Default, Debug, AggregateName)]
678654
pub struct DemoPercentileDisc;
679655

680656
#[pg_aggregate]
681-
impl Aggregate for DemoPercentileDisc {
657+
impl Aggregate<DemoPercentileDisc> for DemoPercentileDisc {
682658
type Args = name!(input, i32);
683659
type State = Internal;
684660
type Finalize = i32;
@@ -732,13 +708,13 @@ We can test it like so:
732708

733709
```sql
734710
SELECT DemoPercentileDisc(0.5) WITHIN GROUP (ORDER BY income) FROM UNNEST(ARRAY [6000, 70000, 500]) as income;
735-
-- demopercentiledisc
711+
-- demopercentiledisc
736712
-- --------------------
737713
-- 6000
738714
-- (1 row)
739715

740716
SELECT DemoPercentileDisc(0.05) WITHIN GROUP (ORDER BY income) FROM UNNEST(ARRAY [5, 100000000, 6000, 70000, 500]) as income;
741-
-- demopercentiledisc
717+
-- demopercentiledisc
742718
-- --------------------
743719
-- 5
744720
-- (1 row)
@@ -845,7 +821,7 @@ SELECT demo_sum(value) OVER (
845821
-- LOG: moving_state(0, 20)
846822
-- LOG: moving_state(0, 300)
847823
-- LOG: moving_state(0, 4000)
848-
-- demo_sum
824+
-- demo_sum
849825
-- ----------
850826
-- 1
851827
-- 20
@@ -864,7 +840,7 @@ SELECT demo_sum(value) OVER (
864840
-- LOG: moving_state(1, 20)
865841
-- LOG: moving_state(21, 300)
866842
-- LOG: moving_state(321, 4000)
867-
-- demo_sum
843+
-- demo_sum
868844
-- ----------
869845
-- 4321
870846
-- 4321
@@ -881,7 +857,7 @@ SELECT demo_sum(value) OVER (
881857
-- LOG: moving_state(20, 300)
882858
-- LOG: moving_state_inverse(320, 20)
883859
-- LOG: moving_state(300, 4000)
884-
-- demo_sum
860+
-- demo_sum
885861
-- ----------
886862
-- 1
887863
-- 21
@@ -899,7 +875,7 @@ SELECT demo_sum(value) OVER (
899875
-- LOG: moving_state(0, 10000)
900876
-- LOG: moving_state(10000, 1)
901877
-- LOG: moving_state_inverse(10001, 10000)
902-
-- demo_sum
878+
-- demo_sum
903879
-- ----------
904880
-- 10001
905881
-- 1

pgrx-examples/aggregate/src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ use std::str::FromStr;
1616

1717
pgrx::pg_module_magic!(name, version);
1818

19-
#[derive(Copy, Clone, PostgresType, Serialize, Deserialize)]
19+
#[derive(Copy, Clone, PostgresType, Serialize, Deserialize, AggregateName)]
20+
#[aggregate_name = "DEMOAVG"]
2021
#[pgvarlena_inoutfuncs]
2122
#[derive(Default)]
2223
pub struct IntegerAvgState {
@@ -27,9 +28,9 @@ pub struct IntegerAvgState {
2728
impl IntegerAvgState {
2829
#[inline(always)]
2930
fn state(
30-
mut current: <Self as Aggregate>::State,
31-
arg: <Self as Aggregate>::Args,
32-
) -> <Self as Aggregate>::State {
31+
mut current: <Self as Aggregate<Self>>::State,
32+
arg: <Self as Aggregate<Self>>::Args,
33+
) -> <Self as Aggregate<Self>>::State {
3334
if let Some(arg) = arg {
3435
current.sum += arg;
3536
current.n += 1;
@@ -38,7 +39,7 @@ impl IntegerAvgState {
3839
}
3940

4041
#[inline(always)]
41-
fn finalize(current: <Self as Aggregate>::State) -> <Self as Aggregate>::Finalize {
42+
fn finalize(current: <Self as Aggregate<Self>>::State) -> <Self as Aggregate<Self>>::Finalize {
4243
current.sum / current.n
4344
}
4445
}
@@ -74,10 +75,9 @@ impl PgVarlenaInOutFuncs for IntegerAvgState {
7475
// In order to improve the testability of your code, it's encouraged to make this implementation
7576
// call to your own functions which don't require a PostgreSQL made [`pgrx::pg_sys::FunctionCallInfo`].
7677
#[pg_aggregate]
77-
impl Aggregate for IntegerAvgState {
78+
impl Aggregate<IntegerAvgState> for IntegerAvgState {
7879
type State = PgVarlena<Self>;
7980
type Args = pgrx::name!(value, Option<i32>);
80-
const NAME: &'static str = "DEMOAVG";
8181

8282
const INITIAL_CONDITION: Option<&'static str> = Some("0,0");
8383

pgrx-macros/src/lib.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,51 @@ pub fn derive_postgres_hash(input: TokenStream) -> TokenStream {
12901290
deriving_postgres_hash(ast).unwrap_or_else(syn::Error::into_compile_error).into()
12911291
}
12921292

1293+
/// Derives the `ToAggregateName` trait.
1294+
#[proc_macro_derive(AggregateName, attributes(aggregate_name))]
1295+
pub fn derive_aggregate_name(input: TokenStream) -> TokenStream {
1296+
let ast = parse_macro_input!(input as syn::DeriveInput);
1297+
1298+
impl_aggregate_name(ast).unwrap_or_else(|e| e.into_compile_error()).into()
1299+
}
1300+
1301+
fn impl_aggregate_name(ast: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
1302+
let name = &ast.ident;
1303+
1304+
let mut custom_name_value: Option<String> = None;
1305+
1306+
for attr in &ast.attrs {
1307+
if attr.path().is_ident("aggregate_name") {
1308+
let meta = &attr.meta;
1309+
match meta {
1310+
syn::Meta::NameValue(syn::MetaNameValue {
1311+
value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }),
1312+
..
1313+
}) => {
1314+
custom_name_value = Some(s.value());
1315+
break;
1316+
}
1317+
_ => {
1318+
return Err(syn::Error::new_spanned(
1319+
attr,
1320+
"#[aggregate_name] must be in the form `#[aggregate_name = \"string_literal\"]`",
1321+
));
1322+
}
1323+
}
1324+
}
1325+
}
1326+
1327+
let name_str = custom_name_value.unwrap_or(name.to_string());
1328+
1329+
let expanded = quote! {
1330+
impl ::pgrx::aggregate::ToAggregateName for #name {
1331+
const NAME: &'static str = #name_str;
1332+
}
1333+
};
1334+
1335+
Ok(expanded)
1336+
}
1337+
12931338
/**
12941339
Declare a `pgrx::Aggregate` implementation on a type as able to used by Postgres as an aggregate.
12951340

0 commit comments

Comments
 (0)