Skip to content

Commit 09f8b21

Browse files
committed
Building upon @yrashk's work in #902, this does a few small things, which caused a bunch of files (mostly tests and examples) to be updated.
First off, the various Spi functions like `Spi::get_one()` go back to returning `-> Result<Option<T>, Error>`. This might seem counter to my comment at #902 (review), but there's an important thing Spi **must** support: Any Datum **can be NULL**, which means we must return `Option<T>`. So, the thing that makes this better is we also now have `impl<T, E> IntoDatum for Result<T, E> where T: IntoDatum, E: std::error::Error`. This means any `#[pg_extern]` function can now return a `Result`, and if that Result represents an error, it is raised as a Postgres ERROR. This is a big win for `#[pg_extern]`-style functions in general, and makes working with an Spi interface that returns `Result<Option<T>, Error>` pretty fluent as now you can just directly return the result from `Spi::get_XXX()` or you can use the `?` operator to nicely unwrap the error and then deal with the returned, possibly NULL, Datum. Doing this, and updating the Spi API caused all sorts of code to be touched in the tests and examples. As a result, there's (hacky) support for `#[pg_test]` functions being able to return `-> Result<(), pgx::spi::Error>`, which makes using Spi in the tests nearly the same, but now with a `?` operator and a `return Ok(())` at the end of such functions. The examples that use SPI needed to be touched as well. This is a pretty big breaking API change, afterall.
1 parent c16446f commit 09f8b21

40 files changed

+821
-548
lines changed

pgx-examples/aggregate/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,20 +157,22 @@ mod tests {
157157
}
158158

159159
#[pg_test]
160-
fn test_integer_avg_state_sql() {
160+
fn test_integer_avg_state_sql() -> Result<(), pgx::spi::Error> {
161161
Spi::run("CREATE TABLE demo_table (value INTEGER);");
162162
Spi::run("INSERT INTO demo_table (value) VALUES (1), (2), (3);");
163-
let retval = Spi::get_one::<i32>("SELECT DEMOAVG(value) FROM demo_table;")
163+
let retval = Spi::get_one::<i32>("SELECT DEMOAVG(value) FROM demo_table;")?
164164
.expect("SQL select failed");
165165
assert_eq!(retval, 2);
166+
Ok(())
166167
}
167168
#[pg_test]
168-
fn test_integer_avg_with_null() {
169+
fn test_integer_avg_with_null() -> Result<(), pgx::spi::Error> {
169170
Spi::run("CREATE TABLE demo_table (value INTEGER);");
170171
Spi::run("INSERT INTO demo_table (value) VALUES (1), (NULL), (3);");
171-
let retval = Spi::get_one::<i32>("SELECT DEMOAVG(value) FROM demo_table;")
172+
let retval = Spi::get_one::<i32>("SELECT DEMOAVG(value) FROM demo_table;")?
172173
.expect("SQL select failed");
173174
assert_eq!(retval, 2);
175+
Ok(())
174176
}
175177
}
176178

pgx-examples/arrays/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,11 @@ pub mod tests {
121121

122122
#[pg_test]
123123
#[search_path(@extschema@)]
124-
fn test_vec_of_customtype() {
124+
fn test_vec_of_customtype() -> Result<(), pgx::spi::Error> {
125125
let customvec =
126-
Spi::get_one::<Vec<SomeStruct>>("SELECT arrays.return_vec_of_customtype();")
126+
Spi::get_one::<Vec<SomeStruct>>("SELECT arrays.return_vec_of_customtype();")?
127127
.expect("SQL select failed");
128128
assert_eq!(customvec, vec![SomeStruct {}]);
129+
Ok(())
129130
}
130131
}

pgx-examples/bytea/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,17 @@ mod tests {
4242
use pgx::prelude::*;
4343

4444
#[pg_test]
45-
fn test_gzip_text() {
46-
let result = Spi::get_one::<String>("SELECT gunzip_as_text(gzip('hi there'));").unwrap();
45+
fn test_gzip_text() -> Result<(), pgx::spi::Error> {
46+
let result = Spi::get_one::<String>("SELECT gunzip_as_text(gzip('hi there'));")?.unwrap();
4747
assert_eq!(result, "hi there");
48+
Ok(())
4849
}
4950

5051
#[pg_test]
51-
fn test_gzip_bytes() {
52-
let result = Spi::get_one::<&[u8]>("SELECT gunzip(gzip('hi there'::bytea));").unwrap();
52+
fn test_gzip_bytes() -> Result<(), pgx::spi::Error> {
53+
let result = Spi::get_one::<&[u8]>("SELECT gunzip(gzip('hi there'::bytea));")?.unwrap();
5354
assert_eq!(result, b"hi there");
55+
Ok(())
5456
}
5557
}
5658

pgx-examples/composite_type/src/lib.rs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -257,58 +257,63 @@ mod tests {
257257
use pgx::AllocatedByRust;
258258

259259
#[pg_test]
260-
fn test_create_dog() {
260+
fn test_create_dog() -> Result<(), pgx::spi::Error> {
261261
let retval = Spi::get_one::<PgHeapTuple<AllocatedByRust>>(
262262
"\
263263
SELECT create_dog('Nami', 0)
264264
",
265-
)
265+
)?
266266
.expect("SQL select failed");
267-
assert_eq!(retval.get_by_name::<&str>("name").unwrap().unwrap(), "Nami");
268-
assert_eq!(retval.get_by_name::<i32>("scritches").unwrap().unwrap(), 0);
267+
assert_eq!(retval.get_by_name::<&str>("name")?.unwrap(), "Nami");
268+
assert_eq!(retval.get_by_name::<i32>("scritches")?.unwrap(), 0);
269+
Ok(())
269270
}
270271

271272
#[pg_test]
272-
fn test_scritch_dog() {
273+
fn test_scritch_dog() -> Result<(), pgx::spi::Error> {
273274
let retval = Spi::get_one::<PgHeapTuple<AllocatedByRust>>(
274275
"\
275276
SELECT scritch_dog(ROW('Nami', 1)::Dog)
276277
",
277-
)
278+
)?
278279
.expect("SQL select failed");
279-
assert_eq!(retval.get_by_name::<&str>("name").unwrap().unwrap(), "Nami");
280-
assert_eq!(retval.get_by_name::<i32>("scritches").unwrap().unwrap(), 1);
280+
assert_eq!(retval.get_by_name::<&str>("name")?.unwrap(), "Nami");
281+
assert_eq!(retval.get_by_name::<i32>("scritches")?.unwrap(), 1);
282+
Ok(())
281283
}
282284

283285
#[pg_test]
284-
fn test_make_friendship() {
286+
fn test_make_friendship() -> Result<(), pgx::spi::Error> {
285287
let friendship = Spi::get_one::<PgHeapTuple<AllocatedByRust>>(
286288
"\
287289
SELECT make_friendship(ROW('Nami', 0)::Dog, ROW('Sally', 0)::Cat)
288290
",
289-
)
291+
)?
290292
.expect("SQL select failed");
291-
let dog: PgHeapTuple<AllocatedByRust> = friendship.get_by_name("dog").unwrap().unwrap();
292-
assert_eq!(dog.get_by_name::<&str>("name").unwrap().unwrap(), "Nami");
293+
let dog: PgHeapTuple<AllocatedByRust> = friendship.get_by_name("dog")?.unwrap();
294+
assert_eq!(dog.get_by_name::<&str>("name")?.unwrap(), "Nami");
293295

294-
let cat: PgHeapTuple<AllocatedByRust> = friendship.get_by_name("cat").unwrap().unwrap();
295-
assert_eq!(cat.get_by_name::<&str>("name").unwrap().unwrap(), "Sally");
296+
let cat: PgHeapTuple<AllocatedByRust> = friendship.get_by_name("cat")?.unwrap();
297+
assert_eq!(cat.get_by_name::<&str>("name")?.unwrap(), "Sally");
298+
Ok(())
296299
}
297300

298301
#[pg_test]
299-
fn test_scritch_collector() {
302+
fn test_scritch_collector() -> Result<(), pgx::spi::Error> {
300303
let retval = Spi::get_one::<i32>(
301304
"SELECT (scritchcollector(value)).scritches FROM UNNEST(ARRAY [1,2,3]) as value;",
302-
)
305+
)?
303306
.expect("SQL select failed");
304307
assert_eq!(retval, 6);
308+
Ok(())
305309
}
306310

307311
#[pg_test]
308-
fn test_dog_add_operator() {
309-
let retval = Spi::get_one::<i32>("SELECT (ROW('Nami', 0)::Dog + 1).scritches;")
312+
fn test_dog_add_operator() -> Result<(), pgx::spi::Error> {
313+
let retval = Spi::get_one::<i32>("SELECT (ROW('Nami', 0)::Dog + 1).scritches;")?
310314
.expect("SQL select failed");
311315
assert_eq!(retval, 1);
316+
Ok(())
312317
}
313318
}
314319

pgx-examples/custom_types/src/complex.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ mod tests {
6161

6262
#[cfg(not(feature = "no-schema-generation"))]
6363
#[pg_test]
64-
fn test_known_animals_via_spi() {
65-
let animals = Spi::get_one::<Animals>("SELECT known_animals();").unwrap();
64+
fn test_known_animals_via_spi() -> Result<(), pgx::spi::Error> {
65+
let animals = Spi::get_one::<Animals>("SELECT known_animals();")?.unwrap();
6666

6767
assert_eq!(animals, known_animals());
6868

@@ -72,6 +72,7 @@ mod tests {
7272
names: vec!["Sally".into(), "Brandy".into(), "anchovy".into()],
7373
age_lookup: hashmap! { 5 => "Sally".into(), 4 => "Brandy".into(), 3=> "anchovy".into()},
7474
}
75-
)
75+
);
76+
Ok(())
7677
}
7778
}

pgx-examples/custom_types/src/ordered.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,15 @@ mod tests {
5050

5151
#[cfg(not(feature = "no-schema-generation"))]
5252
#[pg_test]
53-
fn test_ordering_via_spi() {
53+
fn test_ordering_via_spi() -> Result<(), pgx::spi::Error> {
5454
let items = Spi::get_one::<Vec<OrderedThing>>(
5555
"SELECT array_agg(i ORDER BY i) FROM (VALUES \
5656
('{\"item\":\"foo\"}'::OrderedThing), \
5757
('{\"item\":\"bar\"}'::OrderedThing), \
5858
('{\"item\":\"Foo\"}'::OrderedThing), \
5959
('{\"item\":\"Bar\"}'::OrderedThing))\
6060
items(i);",
61-
)
61+
)?
6262
.unwrap();
6363

6464
assert_eq!(
@@ -69,6 +69,7 @@ mod tests {
6969
OrderedThing { item: "bar".to_string() },
7070
OrderedThing { item: "foo".to_string() },
7171
]
72-
)
72+
);
73+
Ok(())
7374
}
7475
}

pgx-examples/custom_types/src/rust_enum.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,21 @@ mod tests {
2525

2626
#[cfg(not(feature = "no-schema-generation"))]
2727
#[pg_test]
28-
fn test_some_enum() {
29-
let val = Spi::get_one::<SomeEnum>(r#"SELECT '"hello world"'::SomeEnum"#).unwrap();
28+
fn test_some_enum() -> Result<(), pgx::spi::Error> {
29+
let val = Spi::get_one::<SomeEnum>(r#"SELECT '"hello world"'::SomeEnum"#)?.unwrap();
3030

3131
assert!(matches!(
3232
val,
3333
SomeEnum::String(s) if s == "hello world"
3434
));
3535

36-
let val =
37-
Spi::get_one::<SomeEnum>(r#"SELECT '{"a": 1, "s": "hello world"}'::SomeEnum"#).unwrap();
36+
let val = Spi::get_one::<SomeEnum>(r#"SELECT '{"a": 1, "s": "hello world"}'::SomeEnum"#)?
37+
.unwrap();
3838

3939
assert!(matches!(
4040
val,
4141
SomeEnum::Struct{a: 1, s } if s == "hello world"
4242
));
43+
Ok(())
4344
}
4445
}

pgx-examples/schemas/src/lib.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,30 +71,33 @@ mod tests {
7171
use pgx::prelude::*;
7272

7373
#[pg_test]
74-
fn test_hello_default_schema() {
74+
fn test_hello_default_schema() -> Result<(), pgx::spi::Error> {
7575
assert_eq!(
7676
"Hello from the schema where you installed this extension",
77-
Spi::get_one::<&str>("SELECT hello_default_schema()").unwrap()
77+
Spi::get_one::<&str>("SELECT hello_default_schema()")?.unwrap()
7878
);
79+
Ok(())
7980
}
8081

8182
#[pg_test]
82-
fn test_my_type() {
83+
fn test_my_type() -> Result<(), pgx::spi::Error> {
8384
assert_eq!(
8485
"test",
8586
// we don't need to qualify "MyType" because whatever schema it was created in
8687
// is applied to the "search_path" of this test function
87-
Spi::get_one::<MyType>("SELECT '\"test\"'::MyType").unwrap().0
88+
Spi::get_one::<MyType>("SELECT '\"test\"'::MyType")?.unwrap().0
8889
);
90+
Ok(())
8991
}
9092

9193
#[pg_test]
92-
fn test_hello_some_schema() {
94+
fn test_hello_some_schema() -> Result<(), pgx::spi::Error> {
9395
assert_eq!(
9496
"Hello from some_schema",
9597
// "hello_some_schema()" is in "some_schema", so it needs to be qualified
96-
Spi::get_one::<&str>("SELECT some_schema.hello_some_schema()").unwrap()
98+
Spi::get_one::<&str>("SELECT some_schema.hello_some_schema()")?.unwrap()
9799
);
100+
Ok(())
98101
}
99102

100103
#[pg_test]
@@ -107,26 +110,29 @@ mod tests {
107110
c.select("SELECT '\"test\"'::MySomeSchemaType", None, None)
108111
.first()
109112
.get_one::<MySomeSchemaType>()
113+
.expect("get_one::<MySomeSchemaType>() failed")
110114
.unwrap()
111115
.0
112116
);
113117
});
114118
}
115119

116120
#[pg_test]
117-
fn test_my_pg_catalog_type() {
121+
fn test_my_pg_catalog_type() -> Result<(), pgx::spi::Error> {
118122
assert_eq!(
119123
String::from("test"),
120-
Spi::get_one::<MyPgCatalogType>("SELECT '\"test\"'::MyPgCatalogType").unwrap().0
121-
)
124+
Spi::get_one::<MyPgCatalogType>("SELECT '\"test\"'::MyPgCatalogType")?.unwrap().0
125+
);
126+
Ok(())
122127
}
123128

124129
#[pg_test]
125-
fn test_hello_public() {
130+
fn test_hello_public() -> Result<(), pgx::spi::Error> {
126131
assert_eq!(
127132
"Hello from the public schema",
128-
Spi::get_one::<&str>("SELECT hello_public()").unwrap()
129-
)
133+
Spi::get_one::<&str>("SELECT hello_public()")?.unwrap()
134+
);
135+
Ok(())
130136
}
131137
}
132138

pgx-examples/spi/src/lib.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ All rights reserved.
77
Use of this source code is governed by the MIT license that can be found in the LICENSE file.
88
*/
99
use pgx::prelude::*;
10-
use pgx::{info, IntoDatum};
10+
use pgx::{info, spi, IntoDatum};
1111

1212
pgx::pg_module_magic!();
1313

@@ -53,21 +53,20 @@ fn spi_return_query(
5353
}
5454

5555
#[pg_extern(immutable, parallel_safe)]
56-
fn spi_query_random_id() -> Option<i64> {
57-
Spi::get_one("SELECT id FROM spi.spi_example ORDER BY random() LIMIT 1").unwrap()
56+
fn spi_query_random_id() -> Result<Option<i64>, pgx::spi::Error> {
57+
Spi::get_one("SELECT id FROM spi.spi_example ORDER BY random() LIMIT 1")
5858
}
5959

6060
#[pg_extern]
61-
fn spi_query_title(title: &str) -> Option<i64> {
61+
fn spi_query_title(title: &str) -> Result<Option<i64>, pgx::spi::Error> {
6262
Spi::get_one_with_args(
6363
"SELECT id FROM spi.spi_example WHERE title = $1;",
6464
vec![(PgBuiltInOids::TEXTOID.oid(), title.into_datum())],
6565
)
66-
.unwrap()
6766
}
6867

6968
#[pg_extern]
70-
fn spi_query_by_id(id: i64) -> Option<String> {
69+
fn spi_query_by_id(id: i64) -> Result<Option<String>, spi::Error> {
7170
let (returned_id, title) = Spi::connect(|client| {
7271
let tuptable = client
7372
.select(
@@ -78,21 +77,18 @@ fn spi_query_by_id(id: i64) -> Option<String> {
7877
.first();
7978

8079
tuptable.get_two::<i64, String>()
81-
})
82-
.unwrap();
83-
84-
info!("id={}", returned_id);
80+
})?;
8581

86-
Some(title)
82+
info!("id={:?}", returned_id);
83+
Ok(title)
8784
}
8885

8986
#[pg_extern]
90-
fn spi_insert_title(title: &str) -> i64 {
87+
fn spi_insert_title(title: &str) -> Result<Option<i64>, spi::Error> {
9188
Spi::get_one_with_args(
9289
"INSERT INTO spi.spi_example(title) VALUES ($1) RETURNING id",
9390
vec![(PgBuiltInOids::TEXTOID.oid(), title.into_datum())],
9491
)
95-
.unwrap()
9692
}
9793

9894
#[pg_extern]
@@ -108,6 +104,15 @@ fn spi_insert_title2(
108104
TableIterator::once(tuple)
109105
}
110106

107+
#[pg_extern]
108+
fn spi_echo(value: Option<String>) -> Result<Option<String>, spi::Error> {
109+
let result = Spi::get_one_with_args(
110+
"SELECT $1",
111+
vec![(PgOid::from(pg_sys::TEXTOID), value.into_datum())],
112+
);
113+
result
114+
}
115+
111116
extension_sql!(
112117
r#"
113118
@@ -124,15 +129,17 @@ mod tests {
124129
use pgx::prelude::*;
125130

126131
#[pg_test]
127-
fn test_spi_query_by_id_direct() {
128-
assert_eq!(Some("This is a test".to_string()), spi_query_by_id(1))
132+
fn test_spi_query_by_id_direct() -> Result<(), pgx::spi::Error> {
133+
assert_eq!(Some("This is a test".to_string()), spi_query_by_id(1)?);
134+
Ok(())
129135
}
130136

131137
#[pg_test]
132-
fn test_spi_query_by_id_via_spi() {
133-
let result = Spi::get_one::<&str>("SELECT spi.spi_query_by_id(1)").unwrap();
138+
fn test_spi_query_by_id_via_spi() -> Result<(), pgx::spi::Error> {
139+
let result = Spi::get_one::<&str>("SELECT spi.spi_query_by_id(1)")?.unwrap();
134140

135-
assert_eq!("This is a test", result)
141+
assert_eq!("This is a test", result);
142+
Ok(())
136143
}
137144
}
138145

0 commit comments

Comments
 (0)