1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
mod parse;
use darling::FromDeriveInput;
use proc_macro::{self, TokenStream};
use quote::quote;
use syn::parse_macro_input;
use parse::ModelInput;
/// Automatically implement `Model` for your struct.
///
/// ## Attributes
/// * `table` - for structs:
/// - `table_name: String`: optional. Overwrites the table name
/// * `column` - for struct fields:
/// - `dtype: String`: optional. Overwrites the postgres datatype
/// - `unique: bool`: optional, default: `false`. Enables the `unqiue` constraint.
/// - `auto: bool`: optional, default: `false`. This autogenerated the
/// values.
/// - `column_name: String`: optional. Overwrites the column name.
#[proc_macro_derive(Model, attributes(table, column))]
pub fn derive(input: TokenStream) -> TokenStream {
let opts = ModelInput::from_derive_input(&parse_macro_input!(input)).unwrap();
let ident = opts.ident();
let table_name = opts.table_name();
// Retrieve the struct's fields
let fields = opts.fields().collect::<Vec<_>>();
if opts.n_fields() == 0 {
panic!("struct must have at least one field to become a `Model`")
}
let n_fields = opts.n_fields();
// Get the fields' idents
let field_idents = fields
.clone()
.into_iter()
.map(|f| f.ident())
.collect::<Vec<_>>();
let column_names = fields.iter().map(|f| f.column_name()).collect::<Vec<_>>();
let impl_insert = opts.impl_insert();
let table_creation_sql = opts.table_creation_sql();
let impl_column_consts = opts.impl_column_consts();
// Generate the needed impl code
let output = quote!(
#impl_column_consts
impl<'a> TryFrom<&'a pg_worm::Row> for #ident {
type Error = pg_worm::Error;
fn try_from(value: &'a pg_worm::Row) -> Result<#ident, Self::Error> {
// Parse each column into the corresponding field
Ok(#ident {
#(#field_idents: value
.try_get(#column_names)?
),*
})
}
}
#[pg_worm::async_trait]
impl Model<#ident> for #ident {
#[inline]
fn _table_creation_sql() -> &'static str {
#table_creation_sql
}
fn columns() -> &'static [&'static pg_worm::DynCol] {
&#ident::COLUMNS
}
async fn select(filter: pg_worm::Filter) -> Vec<#ident> {
use pg_worm::QueryBuilder;
let query = pg_worm::Query::select(#ident::COLUMNS)
.filter(filter)
.build();
let res: Vec<#ident> = query
.exec()
.await
.expect("failed to query");
res
}
async fn select_one(filter: pg_worm::Filter) -> Option<#ident> {
use pg_worm::QueryBuilder;
let query = pg_worm::Query::select(#ident::COLUMNS)
.filter(filter)
.build();
let res: Vec<#ident> = query
.exec()
.await
.expect("failed to query");
res.into_iter().next()
}
async fn delete(filter: pg_worm::Filter) -> u64 {
// Retrieve client. Panic if not connected
let client = pg_worm::_get_client()
.expect("not connected to db");
// Convert args to correct datatype
let args: Vec<&(dyn pg_worm::pg::types::ToSql + Sync)> = filter
._args()
.into_iter()
.map(|i| &**i as _)
.collect();
// Make the query
let rows_affected = client
.execute(
// Fill in table name and filter
format!(
"DELETE FROM {} {}",
#table_name,
if filter._stmt().is_empty() { "".to_string() }
else { format!("WHERE {}", filter._stmt()) }
).as_str(),
// Pass filter arguments
args.as_slice()
).await.unwrap();
// Return the number of rows affected
rows_affected
}
}
impl #ident {
#impl_insert
const COLUMNS: [&'static pg_worm::DynCol; #n_fields] = [
#(
&#ident::#field_idents
),*
];
}
);
output.into()
}