Skip to content
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

Extend derive(FromRow) with additional features/attributes #156

Open
2 of 4 tasks
mehcode opened this issue Mar 26, 2020 · 2 comments
Open
2 of 4 tasks

Extend derive(FromRow) with additional features/attributes #156

mehcode opened this issue Mar 26, 2020 · 2 comments
Labels
E-medium enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@mehcode
Copy link
Member

mehcode commented Mar 26, 2020

  • Handle #[sqlx(rename = "new_name")] on a struct field

  • Support the derive on a tuple struct and use by ordinal retrieval from the row

  • Handle #[sqlx(ordinal = 3)] to use ordinal retrieval from the row ( row.get(3) )

  • Handle a field that is impl FromRow. A parallel from serde_json would perhaps make this #[sqlx(flatten)].

    #[derive(sqlx::FromRow)]
    struct Bar {
        baz: i32  // row.get("baz")
     }
    
    #[derive(sqlx::FromRow)]
    struct Foo {
        foo: i32, // row.get("foo")
        #[sqlx(flatten)]
        bar: Bar
    }
@gho1b
Copy link

gho1b commented Mar 13, 2022

I wanted same feature to synchronize implementation serde and aync-graphql, then make custom derive for temporary solution

// derive_crate/src/lib.rs
use darling::{ast, Error, FromDeriveInput};
use itertools::Itertools;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

mod args;

#[proc_macro_derive(ExtendedFromRow, attributes(extended))]
pub fn extended_from_row_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let ast = match args::ExtendedFromRow::from_derive_input(&ast) {
        Ok(ast) => ast,
        Err(err) => return TokenStream::from(err.write_errors()),
    };
    let ident = ast.ident;
    let fields = match ast.data {
        ast::Data::Struct(field) => field,
        _ => {
            return TokenStream::from(
                Error::unsupported_format("Input should be a struct").write_errors(),
            )
        }
    };

    let fields = fields
        .into_iter()
        .map(|field| {
            let id = field.ident.unwrap();
            let column = field
                .rename
                .or_else(|| Some(id.to_string().trim_start_matches("r#").to_owned()))
                .unwrap();
            let ty = field.ty;
            match field.flatten.unwrap_or(false) {
                true => quote! {
                    #id: #ty::from_row(row)?
                },
                false => quote! {
                    #id: row.try_get(#column)?
                },
            }
        })
        .collect_vec();

    let extended = quote! {
        impl<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> for #ident {
            fn from_row(row: &'a sqlx::postgres::PgRow) -> Result<Self, sqlx::Error> {
                Ok(Self{#( #fields ),*})
            }
        }
    };
    extended.into()
}
// derive_crate/src/args.rs
use darling::{ast::Data, util::Ignored, FromDeriveInput, FromField};
use proc_macro2::Ident;
use syn::{Attribute, Generics, Type};

#[derive(FromDeriveInput)]
#[darling(attributes(extended), forward_attrs(doc))]
pub struct ExtendedFromRow {
    pub ident: Ident,
    pub generics: Generics,
    pub attrs: Vec<Attribute>,
    pub data: Data<Ignored, ExtendedRowField>,
}

#[derive(FromField)]
#[darling(attributes(extended), forward_attrs(doc))]
pub struct ExtendedRowField {
    pub ident: Option<Ident>,
    pub ty: Type,
    #[darling(default)]
    pub flatten: Option<bool>,
    #[darling(default)]
    pub rename: Option<String>,
}

Then use the derive like this

#[derive(ExtendedFromRow)]
struct FooBar {
pub foo: String,
#[extended(flatten)]
pub bar: Bar,
}

#[derive(FromRow)]
struct Bar {
pub bar: String
}

Then we can use the struct for this operation

let foo_bars: Vec<FooBar> = sqlx::query_as("SELECT * FROM foo_bar").fetch_all(&conn).await?;

the other solution i merge them in tuple struct using macro_rule

// example
#[derive(FromRow)]
struct Foo {
pub foo: String
}

#[derive(FromRow)]
struct Bar {
pub bar: String
}

merge_object!(pub struct FooBar, Foo, Bar);

#[macro_export]
macro_rules! merge_model {
    ($vis:vis struct $model_name:ident, $($member:ident),*) => {
        #[derive(Debug, Clone, Deserialize, Serialize, MergedObject)]
        $vis struct $model_name($( pub $member ),*);

        impl<'a> sqlx::FromRow<'a, sqlx::postgres::PgRow> for $model_name {
            fn from_row(row: &'a sqlx::postgres::PgRow) -> Result<Self, sqlx::Error> {
                Ok(Self($( $member::from_row(row)? ),*))
            }
        }
    };
}

I wish the feature available soon

@ericsampson
Copy link

just for tracking purposes, item 4 was implemented in #1959

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E-medium enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants