sqlx icon indicating copy to clipboard operation
sqlx copied to clipboard

enhancement: (#2934) Allows flatten attribute to work for Optional values when deriving FromRow

Open jonnyso opened this issue 1 year ago • 4 comments

fixes #2934

jonnyso avatar Jan 05 '25 23:01 jonnyso

This change appears innocuous enough, but I'm worried that people won't understand what this impl is for and will assume it does something different, i.e. make .fetch_one() return None instead of erroring on an empty resultset. That's not really possible with our current API design, which makes this impl a potential footgun since it'd compile but behave differently than expected at runtime.

I think I'd prefer if this was something much more specific to #[sqlx(flatten)], and I think we can accomplish that without adding a new public blanket impl, using a trick called autoderef specialization: http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html

I used the same trick in https://github.com/launchbadge/sqlx/pull/3356 although the solution I'm imagining isn't going to look exactly like that.

abonander avatar Jan 06 '25 12:01 abonander

(As for why to use autoderef specialization instead of just checking if the type is wrapped in Option: that can't see through type aliases.)

abonander avatar Jan 06 '25 12:01 abonander

I agree it would be inconsistent with the way .fetch_one() works right now, I'll look into the suggestions you made, thanks !

jonnyso avatar Jan 06 '25 14:01 jonnyso

Hey, @abonander , I managed to get some of the way there using the autoderef trick as you suggested, however for the optional type I'm having difficulty figuring out how to do the bounds properly:

This is the what the macro expands to:

#[automatically_derived]
impl<'a, R: ::sqlx::Row> ::sqlx::FromRow<'a, R> for Bar
where
    &'a ::std::primitive::str: ::sqlx::ColumnIndex<R>,
    u64: ::sqlx::decode::Decode<'a, R::Database>,
    u64: ::sqlx::types::Type<R::Database>,
    String: ::sqlx::decode::Decode<'a, R::Database>,
    String: ::sqlx::types::Type<R::Database>,
    Option<Foo>: ::sqlx::FromRow<'a, R>, // Should be Foo: ::sqlx::FromRow<'a, R>
{
    fn from_row(__row: &'a R) -> ::sqlx::Result<Self> {
        let id: u64 = __row.try_get("id")?;
        let value: String = __row.try_get("value")?;
        let foo: Option<Foo> = {
            use ::sqlx_core::from_row::{FromOptRow, Wrapper};
            let value: Result<Option<Foo>, sqlx::Error> = Wrapper.__from_row(__row);
            value
        }?;
        ::std::result::Result::Ok(Bar { id, value, foo })
    }
}

Is there I way to know what #ty here should be ? Just checking for an Option wouldn't work because of type aliases as you mentioned.

predicates.push(parse_quote!(#ty: ::sqlx::FromRow<#lifetime, R>));

Full code on this branch: https://github.com/jonnyso/sqlx/tree/autoderef_flatten_opt

jonnyso avatar Jan 07 '25 20:01 jonnyso