Skip to content

Commit c66680b

Browse files
authored
Merge pull request #4 from blitss/feat/implement-aggregates
feat: implement aggregates to support min/max
2 parents 4fbe78d + 361e440 commit c66680b

File tree

4 files changed

+148
-14
lines changed

4 files changed

+148
-14
lines changed

Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ name = "typeid"
33
version = "0.0.0"
44
edition = "2021"
55

6-
[build]
7-
rustflags = ["-C", "target-feature=+aes"]
8-
96
[lib]
107
crate-type = ["cdylib", "lib"]
118

src/aggregate.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use pgrx::{aggregate::*, pg_aggregate, pg_sys};
2+
3+
use crate::typeid::TypeID;
4+
5+
pub struct TypeIDMin;
6+
pub struct TypeIDMax;
7+
8+
#[pg_aggregate]
9+
impl Aggregate for TypeIDMin {
10+
const NAME: &'static str = "min";
11+
type Args = TypeID;
12+
type State = Option<TypeID>;
13+
14+
fn state(
15+
current: Self::State,
16+
arg: Self::Args,
17+
_fcinfo: pg_sys::FunctionCallInfo,
18+
) -> Self::State {
19+
match current {
20+
None => Some(arg),
21+
Some(current) => Some(if arg < current { arg } else { current }),
22+
}
23+
}
24+
}
25+
26+
#[pg_aggregate]
27+
impl Aggregate for TypeIDMax {
28+
const NAME: &'static str = "max";
29+
type Args = TypeID;
30+
type State = Option<TypeID>;
31+
32+
fn state(
33+
current: Self::State,
34+
arg: Self::Args,
35+
_fcinfo: pg_sys::FunctionCallInfo,
36+
) -> Self::State {
37+
match current {
38+
None => Some(arg),
39+
Some(current) => Some(if arg > current { arg } else { current }),
40+
}
41+
}
42+
}
43+
44+
#[cfg(any(test, feature = "pg_test"))]
45+
#[pgrx::pg_schema]
46+
mod tests {
47+
use super::*;
48+
use pgrx::prelude::*;
49+
50+
#[pg_test]
51+
fn test_typeid_min_max_aggregates() {
52+
Spi::connect(|mut client| {
53+
// Create a temporary table
54+
client
55+
.update("CREATE TEMPORARY TABLE test_typeid (id typeid)", None, None)
56+
.unwrap();
57+
58+
// Insert some test data
59+
client.update("INSERT INTO test_typeid VALUES (typeid_generate('user')), (typeid_generate('user')), (typeid_generate('user'))", None, None).unwrap();
60+
61+
// Test min aggregate
62+
let result = client
63+
.select("SELECT min(id) FROM test_typeid", None, None)
64+
.unwrap();
65+
66+
assert_eq!(result.len(), 1);
67+
let min_typeid: TypeID = result
68+
.first()
69+
.get_one()
70+
.unwrap()
71+
.expect("didnt get min typeid");
72+
73+
// Test max aggregate
74+
let result = client
75+
.select("SELECT max(id) FROM test_typeid", None, None)
76+
.unwrap();
77+
assert_eq!(result.len(), 1);
78+
let max_typeid: TypeID = result
79+
.first()
80+
.get_one()
81+
.unwrap()
82+
.expect("didnt get max typeid");
83+
84+
// Verify that max is greater than min
85+
assert!(max_typeid > min_typeid);
86+
87+
// Test with empty table
88+
client.update("TRUNCATE test_typeid", None, None).unwrap();
89+
let result = client
90+
.select("SELECT min(id), max(id) FROM test_typeid", None, None)
91+
.unwrap();
92+
assert_eq!(result.len(), 1);
93+
94+
let (min_typeid, max_typeid): (Option<TypeID>, Option<TypeID>) =
95+
result.first().get_two().unwrap();
96+
assert_eq!(min_typeid, None);
97+
assert_eq!(max_typeid, None);
98+
99+
// Test with single value
100+
client
101+
.update(
102+
"INSERT INTO test_typeid VALUES (typeid_generate('user'))",
103+
None,
104+
None,
105+
)
106+
.unwrap();
107+
let result = client
108+
.select("SELECT min(id), max(id) FROM test_typeid", None, None)
109+
.unwrap();
110+
assert_eq!(result.len(), 1);
111+
let (min_typeid, max_typeid): (Option<TypeID>, Option<TypeID>) =
112+
result.first().get_two().unwrap();
113+
114+
assert_eq!(min_typeid.unwrap(), max_typeid.unwrap());
115+
116+
// Test with multiple prefixes
117+
client.update("TRUNCATE test_typeid", None, None).unwrap();
118+
client.update("INSERT INTO test_typeid VALUES (typeid_generate('user')), (typeid_generate('post')), (typeid_generate('comment'))", None, None).unwrap();
119+
let result = client
120+
.select("SELECT min(id), max(id) FROM test_typeid", None, None)
121+
.unwrap();
122+
assert_eq!(result.len(), 1);
123+
let (min_typeid, max_typeid): (Option<TypeID>, Option<TypeID>) =
124+
result.first().get_two().unwrap();
125+
126+
assert!(min_typeid.unwrap().type_prefix() == "comment");
127+
assert!(max_typeid.unwrap().type_prefix() == "user");
128+
})
129+
}
130+
}

src/lib.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod aggregate;
12
pub mod base32;
23
pub mod typeid;
34

@@ -32,10 +33,7 @@ fn uuid_to_typeid(prefix: &str, uuid: pgrx::Uuid) -> TypeID {
3233

3334
#[pg_extern]
3435
fn typeid_cmp(a: TypeID, b: TypeID) -> i32 {
35-
match a.type_prefix().cmp(b.type_prefix()) {
36-
std::cmp::Ordering::Equal => a.uuid().cmp(b.uuid()) as i32,
37-
other => other as i32,
38-
}
36+
a.cmp(&b) as i32
3937
}
4038

4139
#[pg_extern]
@@ -198,7 +196,6 @@ mod tests {
198196
Spi::run("CREATE TABLE question (id typeid);").unwrap();
199197
Spi::run("CREATE TABLE answer (id typeid, question typeid);").unwrap();
200198

201-
println!("Creating tables");
202199
// Generate and insert test data
203200
let typeid1 = typeid_generate("qual");
204201
let typeid2 = typeid_generate("answer");
@@ -236,7 +233,6 @@ mod tests {
236233
.unwrap()
237234
.expect("expected to find oid");
238235

239-
println!("Inserting into table: {:?}", oid);
240236
Spi::run_with_args(
241237
&query,
242238
Some(vec![
@@ -251,7 +247,6 @@ mod tests {
251247
let query = format!("INSERT INTO {} (id) VALUES ($1::typeid)", table_name);
252248
let oid = oid_for_type("typeid").unwrap();
253249

254-
println!("Inserting into table: {:?}", oid.unwrap());
255250
Spi::run_with_args(
256251
&query,
257252
Some(vec![(

src/typeid.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use core::fmt;
2-
use std::borrow::Cow;
2+
use std::{borrow::Cow, cmp::Ordering};
33

44
use pgrx::prelude::*;
55
use serde::{Deserialize, Serialize};
@@ -25,7 +25,7 @@ pub enum Error {
2525
InvalidData,
2626
}
2727

28-
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
28+
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, PartialOrd)]
2929
pub struct TypeIDPrefix(String);
3030

3131
impl TypeIDPrefix {
@@ -73,7 +73,7 @@ impl TypeIDPrefix {
7373
}
7474
}
7575

76-
#[derive(Serialize, Deserialize, Clone, PostgresType, PartialEq, Eq)]
76+
#[derive(Debug, Serialize, Deserialize, Clone, PostgresType, PartialOrd, PartialEq, Eq)]
7777
#[inoutfuncs]
7878
pub struct TypeID(TypeIDPrefix, Uuid);
7979

@@ -108,6 +108,15 @@ impl TypeID {
108108
}
109109
}
110110

111+
impl Ord for TypeID {
112+
fn cmp(&self, b: &Self) -> Ordering {
113+
match self.type_prefix().cmp(b.type_prefix()) {
114+
std::cmp::Ordering::Equal => self.uuid().cmp(b.uuid()),
115+
other => other,
116+
}
117+
}
118+
}
119+
111120
impl Hash for TypeID {
112121
fn hash<H: Hasher>(&self, state: &mut H) {
113122
self.type_prefix().as_bytes().hash(state);
@@ -135,7 +144,10 @@ impl InOutFuncs for TypeID {
135144
// Convert the input to a str and handle potential UTF-8 errors
136145
let str_input = input.to_str().expect("text input is not valid UTF8");
137146

138-
TypeID::from_string(str_input).unwrap()
147+
match TypeID::from_string(str_input) {
148+
Ok(typeid) => typeid,
149+
Err(err) => panic!("Failed to construct TypeId<{str_input}>: {err}"),
150+
}
139151
}
140152

141153
fn output(&self, buffer: &mut pgrx::StringInfo) {

0 commit comments

Comments
 (0)