Skip to content

Commit 4fbe78d

Browse files
authored
Merge pull request #3 from blitss/feat/hash-indexes
feat: add support for hash indexes (requires aes now)
2 parents dc57b62 + 63c4a38 commit 4fbe78d

File tree

4 files changed

+133
-4
lines changed

4 files changed

+133
-4
lines changed

.cargo/config.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[target.'cfg(target_os="macos")']
22
# Postgres symbols won't be available until runtime
3-
rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup"]
3+
rustflags = ["-Clink-arg=-Wl,-undefined,dynamic_lookup", "-C target-feature=+aes,+sse2"]
4+
5+
[target.'cfg(target_arch = "x86_64")']
6+
rustflags = ["-C", "target-feature=+aes"]

Cargo.toml

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

6+
[build]
7+
rustflags = ["-C", "target-feature=+aes"]
8+
69
[lib]
710
crate-type = ["cdylib", "lib"]
811

@@ -17,6 +20,7 @@ pg16 = ["pgrx/pg16", "pgrx-tests/pg16" ]
1720
pg_test = []
1821

1922
[dependencies]
23+
gxhash = { version = "3.4.1" }
2024
pgrx = "=0.11.4"
2125
serde = "1.0.203"
2226
thiserror = "1.0.61"
@@ -39,4 +43,4 @@ codegen-units = 1
3943
[[test]]
4044
name = "spec"
4145
path = "tests/spec.rs"
42-
harness = false
46+
harness = false

src/lib.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use uuid::Uuid;
88

99
use pgrx::prelude::*;
1010

11+
use std::hash::{Hash, Hasher};
12+
1113
pgrx::pg_module_magic!();
1214

1315
#[pg_extern]
@@ -66,6 +68,21 @@ fn typeid_ne(a: TypeID, b: TypeID) -> bool {
6668
typeid_cmp(a, b) != 0
6769
}
6870

71+
#[pg_extern]
72+
fn typeid_hash(typeid: TypeID) -> i32 {
73+
let mut hasher = gxhash::GxHasher::default();
74+
typeid.hash(&mut hasher);
75+
hasher.finish() as i32
76+
}
77+
78+
#[pg_extern]
79+
fn typeid_hash_extended(typeid: TypeID, seed: i64) -> i64 {
80+
let mut hasher = gxhash::GxHasher::with_seed(seed);
81+
82+
typeid.hash(&mut hasher);
83+
hasher.finish() as i64
84+
}
85+
6986
extension_sql! {
7087
r#"
7188
CREATE OPERATOR < (
@@ -115,6 +132,13 @@ r#"
115132
OPERATOR 4 >= (typeid, typeid),
116133
OPERATOR 5 > (typeid, typeid),
117134
FUNCTION 1 typeid_cmp(typeid, typeid);
135+
136+
CREATE OPERATOR FAMILY typeid_hash_ops USING hash;
137+
138+
CREATE OPERATOR CLASS typeid_hash_ops DEFAULT FOR TYPE typeid USING hash AS
139+
OPERATOR 1 = (typeid, typeid),
140+
FUNCTION 1 typeid_hash(typeid),
141+
FUNCTION 2 typeid_hash_extended(typeid, bigint);
118142
"#,
119143
name = "create_typeid_operator_class",
120144
finalize,
@@ -129,6 +153,7 @@ fn uuid_generate_v7() -> pgrx::Uuid {
129153
#[cfg(any(test, feature = "pg_test"))]
130154
#[pg_schema]
131155
mod tests {
156+
use crate::TypeID;
132157
use pgrx::prelude::*;
133158
use uuid::Uuid;
134159

@@ -147,6 +172,95 @@ mod tests {
147172

148173
assert_eq!(converted.get_version_num(), 7);
149174
}
175+
176+
#[pg_test]
177+
fn test_hashing() {
178+
use crate::typeid_hash;
179+
use crate::TypeID;
180+
181+
let id = TypeID::from_string("qual_01j1acv2aeehk8hcapaw7qyjvq").unwrap();
182+
let id2 = TypeID::from_string("qual_01j1acv2aeehk8hcapaw7qyjvq").unwrap();
183+
184+
let hash = typeid_hash(id);
185+
let hash2 = typeid_hash(id2);
186+
println!("UUID: {:?}", hash);
187+
188+
assert_eq!(
189+
hash, hash2,
190+
"Hashes should be consistent for the same input"
191+
);
192+
}
193+
194+
#[pg_test]
195+
fn test_custom_type_in_query() {
196+
use crate::typeid_generate;
197+
// Create tables
198+
Spi::run("CREATE TABLE question (id typeid);").unwrap();
199+
Spi::run("CREATE TABLE answer (id typeid, question typeid);").unwrap();
200+
201+
println!("Creating tables");
202+
// Generate and insert test data
203+
let typeid1 = typeid_generate("qual");
204+
let typeid2 = typeid_generate("answer");
205+
let typeid3 = typeid_generate("answer");
206+
207+
insert_into_table("question", &typeid1);
208+
209+
insert_answer(&typeid2, &typeid1);
210+
insert_answer(&typeid3, &typeid1);
211+
212+
// Execute the query and check results
213+
let result = Spi::get_one::<i64>(
214+
"SELECT COUNT(*) FROM answer WHERE question IN (SELECT id FROM question)",
215+
)
216+
.unwrap();
217+
assert_eq!(result, Some(2));
218+
}
219+
220+
fn oid_for_type(type_name: &str) -> Result<Option<PgOid>, pgrx::spi::Error> {
221+
use crate::pg_sys::Oid;
222+
223+
let oid = Spi::get_one_with_args::<u32>(
224+
"SELECT oid FROM pg_type WHERE typname = $1",
225+
vec![(PgBuiltInOids::TEXTOID.oid(), type_name.into_datum())],
226+
)?;
227+
Ok(oid.map(|oid| PgOid::from(Oid::from(oid))))
228+
}
229+
230+
fn insert_answer(typeid: &TypeID, reference: &TypeID) {
231+
let query = format!(
232+
"INSERT INTO {} (id, question) VALUES ($1::typeid, $2::typeid)",
233+
"answer"
234+
);
235+
let oid = oid_for_type("typeid")
236+
.unwrap()
237+
.expect("expected to find oid");
238+
239+
println!("Inserting into table: {:?}", oid);
240+
Spi::run_with_args(
241+
&query,
242+
Some(vec![
243+
(oid, typeid.clone().into_datum()),
244+
(oid, reference.clone().into_datum()),
245+
]),
246+
)
247+
.unwrap();
248+
}
249+
250+
fn insert_into_table(table_name: &str, typeid: &TypeID) {
251+
let query = format!("INSERT INTO {} (id) VALUES ($1::typeid)", table_name);
252+
let oid = oid_for_type("typeid").unwrap();
253+
254+
println!("Inserting into table: {:?}", oid.unwrap());
255+
Spi::run_with_args(
256+
&query,
257+
Some(vec![(
258+
oid.expect("expected to find oid"),
259+
typeid.clone().into_datum(),
260+
)]),
261+
)
262+
.unwrap();
263+
}
150264
}
151265

152266
/// This module is required by `cargo pgrx test` invocations.

src/typeid.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::borrow::Cow;
33

44
use pgrx::prelude::*;
55
use serde::{Deserialize, Serialize};
6+
use std::hash::{Hash, Hasher};
67
use uuid::Uuid;
78

89
use crate::base32::{decode_base32_uuid, encode_base32_uuid};
@@ -24,7 +25,7 @@ pub enum Error {
2425
InvalidData,
2526
}
2627

27-
#[derive(Serialize, Deserialize)]
28+
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
2829
pub struct TypeIDPrefix(String);
2930

3031
impl TypeIDPrefix {
@@ -72,7 +73,7 @@ impl TypeIDPrefix {
7273
}
7374
}
7475

75-
#[derive(Serialize, Deserialize, PostgresType)]
76+
#[derive(Serialize, Deserialize, Clone, PostgresType, PartialEq, Eq)]
7677
#[inoutfuncs]
7778
pub struct TypeID(TypeIDPrefix, Uuid);
7879

@@ -107,6 +108,13 @@ impl TypeID {
107108
}
108109
}
109110

111+
impl Hash for TypeID {
112+
fn hash<H: Hasher>(&self, state: &mut H) {
113+
self.type_prefix().as_bytes().hash(state);
114+
self.uuid().hash(state);
115+
}
116+
}
117+
110118
impl fmt::Display for TypeID {
111119
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112120
if self.type_prefix().is_empty() {

0 commit comments

Comments
 (0)