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

Postgres: get_by_any getters #15

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions example-postgres/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// #![feature(trace_macros)]
use chrono::{NaiveDateTime, Utc};
use ormx::{Insert, Table};
use sqlx::PgPool;
use sqlx::{PgConnection, PgPool};

// trace_macros!(true);

Expand All @@ -18,6 +18,7 @@ async fn main() -> anyhow::Result<()> {
.init()?;

let db = PgPool::connect(&dotenv::var("DATABASE_URL")?).await?;
let mut conn = db.acquire().await?;

log::info!("insert a new row into the database");
let mut new = InsertUser {
Expand All @@ -28,7 +29,7 @@ async fn main() -> anyhow::Result<()> {
disabled: None,
role: Role::User,
}
.insert(&mut *db.acquire().await?)
.insert(&mut conn)
.await?;

log::info!("update a single field");
Expand Down Expand Up @@ -60,15 +61,41 @@ async fn main() -> anyhow::Result<()> {
log::info!("delete the user from the database");
new.delete(&db).await?;

log::info!("inserting 3 dummy users with ids: 1, 2 & 3");
insert_dummy_user(&mut conn, 2).await?;
insert_dummy_user(&mut conn, 3).await?;
insert_dummy_user(&mut conn, 4).await?;

log::info!("getting many users by any user id (using 'get_any' getter)");
let users = User::get_many_by_user_ids(&mut conn, &[2, 4]).await?;
dbg!(&users);
assert_eq!(users.len(), 2);

log::info!("empty user table");
sqlx::query!("DELETE FROM users").execute(&db).await?;
Ok(())
}

async fn insert_dummy_user(conn: &mut PgConnection, id: i32) -> Result<User, sqlx::Error> {
InsertUser {
user_id: id,
first_name: "Dummy".to_owned(),
last_name: "Dummy".to_owned(),
email: format!("dummy{}@mail.com", id),
disabled: None,
role: Role::User,
}
.insert(conn)
.await
}

#[derive(Debug, ormx::Table)]
#[ormx(table = "users", id = user_id, insertable)]
struct User {
// map this field to the column "id"
#[ormx(column = "id")]
#[ormx(get_one = get_by_user_id)]
#[ormx(get_any = get_many_by_user_ids)]
user_id: i32,
first_name: String,
last_name: String,
Expand Down
11 changes: 9 additions & 2 deletions ormx-macros/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub enum TableFieldAttr {
GetOptional(Getter),
// get_many [= <ident>]? [(<type>)]?
GetMany(Getter),
// get_many [= <ident>]? [(<type>)]?
#[cfg(feature = "postgres")]
GetAny(Getter),
// set [= <ident>]?
Set(Option<Ident>),
}
Expand Down Expand Up @@ -95,14 +98,16 @@ pub fn parse_attrs<A: Parse>(attrs: &[Attribute]) -> Result<Vec<A>> {
macro_rules! impl_parse {
// entry point
($i:ident {
$( $s:literal => $v:ident( $($t:tt)* ) ),*
$( $(#[cfg($cfg_attr: meta)])? $s:literal => $v:ident( $($t:tt)* ) ),*
}) => {
impl syn::parse::Parse for $i {
#[allow(clippy::redundant_closure_call)]
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
match &*ident.to_string() {
$( $s => (impl_parse!($($t)*))(input).map(Self::$v), )*
$(
$(#[cfg($cfg_attr)])?
$s => (impl_parse!($($t)*))(input).map(Self::$v), )*
_ => Err(input.error("unknown attribute"))
}
}
Expand Down Expand Up @@ -141,6 +146,8 @@ impl_parse!(TableFieldAttr {
"get_one" => GetOne(Getter),
"get_optional" => GetOptional(Getter),
"get_many" => GetMany(Getter),
#[cfg(feature = "postgres")]
"get_any" => GetAny(Getter),
"set" => Set((= Ident)?),
"custom_type" => CustomType(),
"default" => Default()
Expand Down
41 changes: 41 additions & 0 deletions ormx-macros/src/backend/postgres/get_any.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::{
backend::{common, postgres::PgBindings},
table::Table,
};

use super::PgBackend;
use proc_macro2::TokenStream;
use quote::quote;

pub(crate) fn impl_get_any_getter(table: &Table<PgBackend>) -> TokenStream {
let column_list = table.select_column_list();
let vis = &table.vis;
let mut getters = TokenStream::new();

for field in table.fields.iter() {
if let Some(getter) = &field.get_any {
let sql = format!(
"SELECT {} FROM {} WHERE {} = ANY({})",
column_list,
table.table,
field.column(),
PgBindings::default().next().unwrap()
);

let func = getter.ident_or_fallback(&field);
let arg = getter.arg_ty.clone().unwrap_or_else(|| {
let ty = &field.ty;
syn::parse2(quote!(&[#ty])).unwrap()
});

getters.extend(common::get_many(vis, &func, &arg, &sql));
}
}

let table_ident = &table.ident;
quote! {
impl #table_ident {
#getters
}
}
}
11 changes: 11 additions & 0 deletions ormx-macros/src/backend/postgres/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ use proc_macro2::TokenStream;
use crate::backend::Backend;
use crate::table::Table;

use self::get_any::impl_get_any_getter;

use super::common;

mod get_any;
mod insert;

#[derive(Clone)]
Expand Down Expand Up @@ -32,6 +37,12 @@ impl Backend for PgBackend {
fn impl_insert(table: &Table<Self>) -> TokenStream {
insert::impl_insert(table)
}

fn impl_getters(table: &Table<Self>) -> TokenStream {
let mut getters = common::getters::<Self>(table);
getters.extend(impl_get_any_getter(table));
getters
}
}

#[derive(Default)]
Expand Down
5 changes: 4 additions & 1 deletion ormx-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ mod utils;
///
/// # Accessors: Getters
/// ormx will generate accessor functions for fields annotated with `#[ormx(get_one)]`,
/// `#[ormx(get_optional)]` and `#[ormx(get_many)]`.
/// `#[ormx(get_optional)]`, `#[ormx(get_many)]` and `#[ormx(get_any)]`.
/// These functions can be used to query a row by the value of the annotated field.
///
/// The generated function will have these signature:
Expand All @@ -63,6 +63,9 @@ mod utils;
/// **`#[ormx(get_many)]`**:
/// `{pub} async fn get_by_{field_name}(&{field_type}) -> Result<Vec<Self>>`
///
/// **`#[ormx(get_any)]`**:
/// `{pub} async fn get_by_{field_name}(&[{field_type}]) -> Result<Vec<Self>>`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if get_by_any would be a better name? The default name of the method would become get_by_any_{field_name}.

get_by_any_email(&[String]) is clearer than get_by_email(&[String]) in my opinion

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree!
Been inactive here for a while, sry. Do you still want to change this? I think get_by_any_* is a far better name. As soon as you are ready, I can merge this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem :) will push an update somewhere this week

///
/// By default, the function will be named `get_by_{field_name)`, though this can be changed by
/// supplying a custom name: `#[ormx(get_one = by_id)]`.
/// By default, the function will take a reference to the type of the annotated field as an argument,
Expand Down
13 changes: 9 additions & 4 deletions ormx-macros/src/table/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub struct TableField<B: Backend> {
pub get_one: Option<Getter>,
pub get_optional: Option<Getter>,
pub get_many: Option<Getter>,
#[cfg(feature = "postgres")]
pub get_any: Option<Getter>,
pub set: Option<Ident>,
pub _phantom: PhantomData<*const B>,
}
Expand Down Expand Up @@ -86,16 +88,19 @@ impl<B: Backend> TableField<B> {

impl Getter {
pub fn or_fallback<B: Backend>(&self, field: &TableField<B>) -> (Ident, Type) {
let ident = self
.func
.clone()
.unwrap_or_else(|| Ident::new(&format!("by_{}", field.field), Span::call_site()));
let ident = self.ident_or_fallback(field);
let arg = self.arg_ty.clone().unwrap_or_else(|| {
let ty = &field.ty;
syn::parse2(quote!(&#ty)).unwrap()
});
(ident, arg)
}

pub fn ident_or_fallback<B: Backend>(&self, field: &TableField<B>) -> Ident {
self.func
.clone()
.unwrap_or_else(|| Ident::new(&format!("by_{}", field.field), Span::call_site()))
}
}

pub fn derive(input: DeriveInput) -> Result<TokenStream> {
Expand Down
7 changes: 7 additions & 0 deletions ormx-macros/src/table/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,18 @@ impl<B: Backend> TryFrom<&syn::Field> for TableField<B> {
default
);

#[cfg(feature = "postgres")]
none!(get_any);

for attr in parse_attrs::<TableFieldAttr>(&value.attrs)? {
match attr {
TableFieldAttr::Column(c) => set_once(&mut column, c)?,
TableFieldAttr::CustomType(..) => set_once(&mut custom_type, true)?,
TableFieldAttr::GetOne(g) => set_once(&mut get_one, g)?,
TableFieldAttr::GetOptional(g) => set_once(&mut get_optional, g)?,
TableFieldAttr::GetMany(g) => set_once(&mut get_many, g)?,
#[cfg(feature = "postgres")]
TableFieldAttr::GetAny(g) => set_once(&mut get_any, g)?,
TableFieldAttr::Set(s) => {
let default = || Ident::new(&format!("set_{}", ident), Span::call_site());
set_once(&mut set, s.unwrap_or_else(default))?
Expand All @@ -62,6 +67,8 @@ impl<B: Backend> TryFrom<&syn::Field> for TableField<B> {
get_one,
get_optional,
get_many,
#[cfg(feature = "postgres")]
get_any,
set,
_phantom: PhantomData,
})
Expand Down