1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput, Field, Fields, parse_macro_input};
4
5mod relations;
6
7#[proc_macro_derive(Model, attributes(has_many, belongs_to, premix))]
8pub fn derive_model(input: TokenStream) -> TokenStream {
9 let input = parse_macro_input!(input as DeriveInput);
10
11 let impl_block = match generate_generic_impl(&input) {
12 Ok(tokens) => tokens,
13 Err(err) => return TokenStream::from(err.to_compile_error()),
14 };
15
16 let rel_block = match relations::impl_relations(&input) {
17 Ok(tokens) => tokens,
18 Err(err) => return TokenStream::from(err.to_compile_error()),
19 };
20
21 TokenStream::from(quote! {
22 #impl_block
23 #rel_block
24 })
25}
26
27fn generate_generic_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
28 let struct_name = &input.ident;
29 let table_name = struct_name.to_string().to_lowercase() + "s";
30
31 let all_fields = if let Data::Struct(data) = &input.data {
32 if let Fields::Named(fields) = &data.fields {
33 &fields.named
34 } else {
35 return Err(syn::Error::new_spanned(
36 &data.fields,
37 "Premix Model only supports structs with named fields",
38 ));
39 }
40 } else {
41 return Err(syn::Error::new_spanned(
42 input,
43 "Premix Model only supports structs",
44 ));
45 };
46
47 let mut db_fields = Vec::new();
48 let mut ignored_field_idents = Vec::new();
49
50 for field in all_fields {
51 if is_ignored(field) {
52 ignored_field_idents.push(field.ident.as_ref().unwrap());
53 } else {
54 db_fields.push(field);
55 }
56 }
57
58 let field_idents: Vec<_> = db_fields
59 .iter()
60 .map(|f| f.ident.as_ref().unwrap())
61 .collect();
62 let field_types: Vec<_> = db_fields.iter().map(|f| &f.ty).collect();
63 let field_indices: Vec<_> = (0..db_fields.len()).collect();
64 let field_names: Vec<_> = field_idents.iter().map(|id| id.to_string()).collect();
65 let field_idents_len = field_idents.len();
66
67 let eager_load_body = relations::generate_eager_load_body(input)?;
68 let has_version = field_names.contains(&"version".to_string());
69 let has_soft_delete = field_names.contains(&"deleted_at".to_string());
70
71 let update_impl = if has_version {
72 quote! {
73 async fn update<'a, E>(&mut self, executor: E) -> Result<premix_core::UpdateResult, premix_core::sqlx::Error>
74 where
75 E: premix_core::IntoExecutor<'a, DB = DB>
76 {
77 let mut executor = executor.into_executor();
78 let table_name = Self::table_name();
79 let set_clause = vec![ #( format!("{} = {}", #field_names, <DB as premix_core::SqlDialect>::placeholder(1 + #field_indices)) ),* ].join(", ");
80 let id_p = <DB as premix_core::SqlDialect>::placeholder(1 + #field_idents_len);
81 let ver_p = <DB as premix_core::SqlDialect>::placeholder(2 + #field_idents_len);
82 let sql = format!(
83 "UPDATE {} SET {}, version = version + 1 WHERE id = {} AND version = {}",
84 table_name, set_clause, id_p, ver_p
85 );
86
87 let mut query = premix_core::sqlx::query::<DB>(&sql)
88 #( .bind(&self.#field_idents) )*
89 .bind(&self.id)
90 .bind(&self.version);
91
92 let result = executor.execute(query).await?;
93
94 if <DB as premix_core::SqlDialect>::rows_affected(&result) == 0 {
95 let exists_p = <DB as premix_core::SqlDialect>::placeholder(1);
96 let exists_sql = format!("SELECT id FROM {} WHERE id = {}", table_name, exists_p);
97 let exists_query = premix_core::sqlx::query_as::<DB, (i32,)>(&exists_sql).bind(&self.id);
98 let exists = executor.fetch_optional(exists_query).await?;
99
100 if exists.is_none() {
101 Ok(premix_core::UpdateResult::NotFound)
102 } else {
103 Ok(premix_core::UpdateResult::VersionConflict)
104 }
105 } else {
106 self.version += 1;
107 Ok(premix_core::UpdateResult::Success)
108 }
109 }
110 }
111 } else {
112 quote! {
113 async fn update<'a, E>(&mut self, executor: E) -> Result<premix_core::UpdateResult, premix_core::sqlx::Error>
114 where
115 E: premix_core::IntoExecutor<'a, DB = DB>
116 {
117 let mut executor = executor.into_executor();
118 let table_name = Self::table_name();
119 let set_clause = vec![ #( format!("{} = {}", #field_names, <DB as premix_core::SqlDialect>::placeholder(1 + #field_indices)) ),* ].join(", ");
120 let id_p = <DB as premix_core::SqlDialect>::placeholder(1 + #field_idents_len);
121 let sql = format!("UPDATE {} SET {} WHERE id = {}", table_name, set_clause, id_p);
122
123 let mut query = premix_core::sqlx::query::<DB>(&sql)
124 #( .bind(&self.#field_idents) )*
125 .bind(&self.id);
126
127 let result = executor.execute(query).await?;
128
129 if <DB as premix_core::SqlDialect>::rows_affected(&result) == 0 {
130 Ok(premix_core::UpdateResult::NotFound)
131 } else {
132 Ok(premix_core::UpdateResult::Success)
133 }
134 }
135 }
136 };
137
138 let delete_impl = if has_soft_delete {
139 quote! {
140 async fn delete<'a, E>(&mut self, executor: E) -> Result<(), premix_core::sqlx::Error>
141 where
142 E: premix_core::IntoExecutor<'a, DB = DB>
143 {
144 let mut executor = executor.into_executor();
145 let table_name = Self::table_name();
146 let id_p = <DB as premix_core::SqlDialect>::placeholder(1);
147 let sql = format!("UPDATE {} SET deleted_at = {} WHERE id = {}", table_name, <DB as premix_core::SqlDialect>::current_timestamp_fn(), id_p);
148
149 let query = premix_core::sqlx::query::<DB>(&sql).bind(&self.id);
150 executor.execute(query).await?;
151
152 self.deleted_at = Some("DELETED".to_string());
153 Ok(())
154 }
155 fn has_soft_delete() -> bool { true }
156 }
157 } else {
158 quote! {
159 async fn delete<'a, E>(&mut self, executor: E) -> Result<(), premix_core::sqlx::Error>
160 where
161 E: premix_core::IntoExecutor<'a, DB = DB>
162 {
163 let mut executor = executor.into_executor();
164 let table_name = Self::table_name();
165 let id_p = <DB as premix_core::SqlDialect>::placeholder(1);
166 let sql = format!("DELETE FROM {} WHERE id = {}", table_name, id_p);
167
168 let query = premix_core::sqlx::query::<DB>(&sql).bind(&self.id);
169 executor.execute(query).await?;
170
171 Ok(())
172 }
173 fn has_soft_delete() -> bool { false }
174 }
175 };
176
177 let mut related_model_bounds = Vec::new();
178 for field in all_fields {
179 for attr in &field.attrs {
180 if (attr.path().is_ident("has_many") || attr.path().is_ident("belongs_to"))
181 && let Ok(related_ident) = attr.parse_args::<syn::Ident>()
182 {
183 related_model_bounds.push(quote! { #related_ident: premix_core::Model<DB> });
184 }
185 }
186 }
187
188 Ok(quote! {
190 impl<'r, R> premix_core::sqlx::FromRow<'r, R> for #struct_name
191 where
192 R: premix_core::sqlx::Row,
193 R::Database: premix_core::sqlx::Database,
194 #(
195 #field_types: premix_core::sqlx::Type<R::Database> + premix_core::sqlx::Decode<'r, R::Database>,
196 )*
197 for<'c> &'c str: premix_core::sqlx::ColumnIndex<R>,
198 {
199 fn from_row(row: &'r R) -> Result<Self, premix_core::sqlx::Error> {
200 use premix_core::sqlx::Row;
201 Ok(Self {
202 #(
203 #field_idents: row.try_get(#field_names)?,
204 )*
205 #(
206 #ignored_field_idents: None,
207 )*
208 })
209 }
210 }
211
212 #[premix_core::async_trait::async_trait]
213 impl<DB> premix_core::Model<DB> for #struct_name
214 where
215 DB: premix_core::SqlDialect,
216 for<'c> &'c str: premix_core::sqlx::ColumnIndex<DB::Row>,
217 usize: premix_core::sqlx::ColumnIndex<DB::Row>,
218 for<'q> <DB as premix_core::sqlx::Database>::Arguments<'q>: premix_core::sqlx::IntoArguments<'q, DB>,
219 for<'c> &'c mut <DB as premix_core::sqlx::Database>::Connection: premix_core::sqlx::Executor<'c, Database = DB>,
220 i32: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
221 i64: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
222 String: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
223 bool: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
224 Option<String>: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
225 #( #related_model_bounds, )*
226 {
227 fn table_name() -> &'static str {
228 #table_name
229 }
230
231 fn create_table_sql() -> String {
232 let mut cols = vec!["id ".to_string() + <DB as premix_core::SqlDialect>::auto_increment_pk()];
233 #(
234 if #field_names != "id" {
235 let field_name: &str = #field_names;
236 let sql_type = if field_name.ends_with("_id") {
237 <DB as premix_core::SqlDialect>::int_type()
238 } else {
239 match field_name {
240 "name" | "title" | "status" | "email" | "role" => <DB as premix_core::SqlDialect>::text_type(),
241 "age" | "version" | "price" | "balance" => <DB as premix_core::SqlDialect>::int_type(),
242 "is_active" => <DB as premix_core::SqlDialect>::bool_type(),
243 "deleted_at" => <DB as premix_core::SqlDialect>::text_type(),
244 _ => <DB as premix_core::SqlDialect>::text_type(),
245 }
246 };
247 cols.push(format!("{} {}", #field_names, sql_type));
248 }
249 )*
250 format!("CREATE TABLE IF NOT EXISTS {} ({})", #table_name, cols.join(", "))
251 }
252
253 fn list_columns() -> Vec<String> {
254 vec![ #( #field_names.to_string() ),* ]
255 }
256
257 async fn save<'a, E>(&mut self, executor: E) -> Result<(), premix_core::sqlx::Error>
258 where
259 E: premix_core::IntoExecutor<'a, DB = DB>
260 {
261 let mut executor = executor.into_executor();
262 use premix_core::ModelHooks;
263 self.before_save().await?;
264
265 let columns: Vec<&str> = vec![ #( #field_names ),* ]
267 .into_iter()
268 .filter(|&c| {
269 if c == "id" { return self.id != 0; }
270 true
271 })
272 .collect();
273
274 let placeholders = (1..=columns.len())
275 .map(|i| <DB as premix_core::SqlDialect>::placeholder(i))
276 .collect::<Vec<_>>()
277 .join(", ");
278
279 let sql = format!("INSERT INTO {} ({}) VALUES ({})", #table_name, columns.join(", "), placeholders);
280
281 let mut query = premix_core::sqlx::query::<DB>(&sql);
282
283 #(
285 if #field_names != "id" {
286 query = query.bind(&self.#field_idents);
287 } else {
288 if self.id != 0 {
289 query = query.bind(&self.id);
290 }
291 }
292 )*
293
294 let result = executor.execute(query).await?;
295
296 let last_id = <DB as premix_core::SqlDialect>::last_insert_id(&result);
298 if last_id > 0 {
299 self.id = last_id as i32;
300 }
301
302 self.after_save().await?;
303 Ok(())
304 }
305
306 #update_impl
307 #delete_impl
308
309 async fn find_by_id<'a, E>(executor: E, id: i32) -> Result<Option<Self>, premix_core::sqlx::Error>
310 where
311 E: premix_core::IntoExecutor<'a, DB = DB>
312 {
313 let mut executor = executor.into_executor();
314 let p = <DB as premix_core::SqlDialect>::placeholder(1);
315 let mut where_clause = format!("WHERE id = {}", p);
316 if Self::has_soft_delete() {
317 where_clause.push_str(" AND deleted_at IS NULL");
318 }
319 let sql = format!("SELECT * FROM {} {} LIMIT 1", #table_name, where_clause);
320 let query = premix_core::sqlx::query_as::<DB, Self>(&sql).bind(id);
321
322 executor.fetch_optional(query).await
323 }
324
325 async fn eager_load<'a, E>(models: &mut [Self], relation: &str, executor: E) -> Result<(), premix_core::sqlx::Error>
326 where
327 E: premix_core::IntoExecutor<'a, DB = DB>
328 {
329 let mut executor = executor.into_executor();
330 #eager_load_body
331 }
332 }
333 })
334}
335
336fn is_ignored(field: &Field) -> bool {
337 for attr in &field.attrs {
338 if attr.path().is_ident("premix")
339 && let Ok(meta) = attr.parse_args::<syn::Ident>()
340 && meta == "ignore"
341 {
342 return true;
343 }
344 }
345 false
346}