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`")
}
// Get the fields' idents
let field_idents = fields
.clone()
.into_iter()
.map(|f| f.clone().ident())
.collect::<Vec<_>>();
let column_names = fields.iter().map(|f| f.column_name()).collect::<Vec<_>>();
let insert_fields = opts.insert_fields().collect::<Vec<_>>();
let insert_columns_counter = (1..=insert_fields.len())
.map(|i| format!("${i}"))
.collect::<Vec<_>>()
.join(", ");
let insert_columns = insert_fields
.clone()
.into_iter()
.map(|f| f.column_name())
.collect::<Vec<_>>()
.join(", ");
let insert_columns_idents = insert_fields
.clone()
.into_iter()
.map(|f| f.ident())
.collect::<Vec<_>>();
let insert_column_dtypes = insert_fields
.clone()
.into_iter()
.map(|f| f.insert_arg_type())
.collect::<Vec<_>>();
let table_creation_sql = opts.table_creation_sql();
// Generate the needed impl code
let output = quote!(
impl<'a> TryFrom<&'a pg_worm::Row> for #ident {
type Error = pg_worm::pg::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).expect("asdasd")),*
})
}
}
#[pg_worm::async_trait]
impl Model<#ident> for #ident {
fn _table_creation_sql() -> &'static str {
#table_creation_sql
}
async fn select() -> Vec<#ident> {
let client = pg_worm::_get_client().expect("not connected to db");
let rows = client.query(format!("SELECT * FROM {}", #table_name).as_str(), &[]).await.unwrap();
rows.iter().map(|r| #ident::try_from(r).expect("couldn't parse data")).collect()
}
async fn select_one() -> Option<#ident> {
let client = pg_worm::_get_client().expect("not connected to db");
let rows = client.query(format!("SELECT * FROM {} LIMIT 1", #table_name).as_str(), &[]).await.unwrap();
if rows.len() != 1 {
return None;
}
Some(#ident::try_from(&rows[0]).unwrap())
}
}
impl #ident {
/// Insert a new entity into the database.
///
/// For columns which are autogenerated (like in the example below, `id`),
/// no value has to be specified.
///
/// # Example
///
/// ```ignore
/// use pg_worm::Model;
///
/// #[derive(Model)]
/// struct Book {
/// #[column(dtype = "BIGSERIAL")]
/// id: i64,
/// #[column(dtype = "TEXT")]
/// title: String
/// }
///
/// async fn some_func() -> Result<(), pg_worm::Error> {
/// Book::insert("Foo".to_string()).await?;
/// }
/// ```
pub async fn insert(
#(#insert_columns_idents: #insert_column_dtypes),*
) -> Result<(), pg_worm::Error> {
let stmt = format!(
"INSERT INTO {} ({}) VALUES ({})",
#table_name,
#insert_columns,
#insert_columns_counter
);
let client = pg_worm::_get_client()?;
client.execute(
stmt.as_str(),
&[
#(&#insert_columns_idents), *
]
).await?;
Ok(())
}
}
);
output.into()
}