-
-
Notifications
You must be signed in to change notification settings - Fork 256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement RetAbi
and BoxRet
#1701
Implement RetAbi
and BoxRet
#1701
Conversation
1c7e5bc
to
6bf40f1
Compare
eaa3633
to
a55501b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the iterator conveniences are basically independent, I'll rebase this on #1703 after that lands.
) -> Result<TableIterator<'static, (name!(id, i32), name!(relname, Option<String>))>, spi::Error> | ||
{ | ||
let oids = vec![1213, 1214, 1232, 1233, 1247, 1249, 1255]; | ||
|
||
TableIterator::new(oids.into_iter().map(|oid| { | ||
(oid, Spi::get_one(&format!("SELECT CAUSE_AN_ERROR FROM pg_class WHERE oid = {oid}"))) | ||
})) | ||
let result = oids | ||
.into_iter() | ||
.map(|oid| { | ||
Ok(( | ||
oid, | ||
Spi::get_one(&format!( | ||
"SELECT CAUSE_AN_ERROR FROM pg_class WHERE oid = {oid}" | ||
))?, | ||
)) | ||
}) | ||
.collect::<Result<Vec<_>, _>>(); | ||
result.map(|v| TableIterator::new(v)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this should be TableIterator<'_, Result<Tuple, Error>>
so that no eager evaluation has to happen. I think I can make that work with a bit more effort. But the schema generator chokes on that! And that is another reason why I want it out of our way.
I do not think people should be required to return TableIterator<'a, (Result, Result, Result, Result, Result, Result)>
if they want to fallibly return a row, but they currently are. And I do not think this should be blocked on that.
Asking people to propagate errors to the top of each row return should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asking people to propagate errors to the top of each row return should be fine.
I'm not sure this is a nice thing to ask. When SPI, for example, is involved, and what you're trying to do is return a TableIterator of a bunch of individual values you somehow got from SPI, it's definitely a lot easier to just return the Result<T>
(or Result<Option<T>>
) in whatever tuple position. Especially if there's quite a few values in the tuple.
This isn't a hill I'm willing to die on, but it'd be nice to support, for example, TableIterator<(Result<S>, T, Result<U>, Result<V>, W, Result<X>, Y, Result<Z>)>
. Then we'll panic whenever we get to unwrapping that result and the user doesn't have to worry about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eeeebbbbrrrr Honestly, we'll never have good UX until we expand to supporting actual struct
arguments, really.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does "actual struct
arguments" mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TableIterator should be able to return a struct that describes the row in question, but this requires being able to use traits like these to return fairly arbitrary generic types. It doesn't work as long as we rely on parsing.
bd44aaa
to
9dd8f5f
Compare
This PR opens a LOT of pure simplifications like 9dd8f5f, it's just going to take forever to find them all. |
.collect::<Vec<_>>(); | ||
Ok(TableIterator::new(filtered)) | ||
.map(|row| { | ||
Ok((row["dog_name"].value()?, row["dog_age"].value()?, row["dog_breed"].value()?)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eeeebbbbrrrr it's certainly not less code but in most cases it just looks like going "hello? I? have? a? question? if that's okay?"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not terrible, but I'm sure you agree that returning a Result from a closure is awkward at best. In this case we .collect::<Result<Vec<_>>>()
so it's not too terrible.
I prefer the left side of this diff, but like I said elsewhere, I'm not willing to die on this hill.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, the left side is a bit nicer, but that type signature... that's what I really have it in for. so yeah, if I didn't believe it would open up better possibilities in short order, I wouldn't do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's what I really have it in for
Yeah, that's why I'm not really gonna fight on this one. I hate typing out all that mess too. But once the return signature has been typed out, it makes it a lot easier to just write some code and return some values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many question marks is much-preferable to having excessively-verbose nested type signatures, yeah.
tests::heap_tuple::tests::pg_test_tuple_desc_clone tests::result_tests::tests::pg_test_return_result_set_of tests::result_tests::tests::pg_test_return_result_set_of_error tests::srf_tests::tests::pg_test_generate_series tests::srf_tests::tests::pg_test_return_none_setof_iterator tests::srf_tests::tests::pg_test_return_some_setof_iterator tests::srf_tests::tests::pg_test_srf_setof_datum_detoasting_with_borrow tests::struct_type_tests::tests::pg_test_complex_storage_and_retrieval
pub fn spi_in_setof() -> Result<SetOfIterator<'static, Option<String>>, spi::Error> { | ||
let oids = vec![1213, 1214, 1232, 1233, 1247, 1249, 1255]; | ||
|
||
SetOfIterator::new(oids.into_iter().map(|oid| { | ||
Spi::get_one(&format!("SELECT CAUSE_AN_ERROR FROM pg_class WHERE oid = {oid}")) | ||
})) | ||
let result = oids | ||
.into_iter() | ||
.map(|oid| { | ||
Spi::get_one(&format!("SELECT CAUSE_AN_ERROR FROM pg_class WHERE oid = {oid}")) | ||
}) | ||
.collect::<Result<Vec<Option<_>>, _>>(); | ||
result.map(SetOfIterator::new) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Breaking this one annoys me much more than the TableIterator nesting case but I want to make sure that SetOfIterator has exactly as stringent requirements, currently, as TableIterator<'a, (T,)>
, because it will make things much worse down the line if they meaningfully differ.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. This really does make code working with the returns from Rust to Postgres much cleaner and more elegant.
I had some confusion about the SetOfIterator type, but it was cleared up in a company meeting. The single-item case behaves differently than the iterator case for legacy Postgres reasons, which is why SetOfIterator can be one item and/or an iterator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the documentation! This explains it neatly.
This allows hiding the details of the type so it cannot be constructed nor meddled with externally, and simplifies many actual cases.
This last commit makes the "Ret" type into an associated type and thus makes the iterators eat all the complexity of their return ABI as now only they have to know about how to drive themselves forward. |
The last few commits were just cleanup. |
Update the doc of `TableIterator`, the generic type was renamed in #1701 but the doc was not updated
This completely reworks the way we handle returning types from functions, so that we no longer have to rely on a macro expansion behavior that has to already know the types at expansion time (and thus has to parse them somehow, which it cannot realistically do because type aliases exist). Instead, we simply expand into code that asks the types themselves to modify the FunctionCallInfo appropriately and then return a raw Datum to Postgres.
This breaks support for certain returns because it was difficult to do this and also support arbitrary nesting, because Postgres does not support arbitrary nesting. For instance, you can no longer return:
SetOfIterator<'a, Result<T, E>>
TableIterator<'a, (Result<T, E>, Result<U, D>)>
Option<TableIterator<'a, Tuple>>
It's expected that this will improve in the near-ish future.
This also breaks returning values from
#[pg_extern]
functions that were relying onIntoDatum
implementations being enough. It is not expected this will improve, for the reasons described on the documentation of the new traits, which can be summarized as "IntoDatum
should never have been used for that bound". This change blocks off several latent correctness problems from affecting pgrx going forward.Fixes #1484