ormlitex_macro/
lib.rs

1#![allow(unused)]
2#![allow(non_snake_case)]
3
4use ormlitex_attr::{ColumnAttributes, ColumnMetadata, TableMetadata, TableMetadataBuilder, ColumnMetadataBuilder, ModelAttributes, SyndecodeError, schema_from_filepaths, LoadOptions, ModelMetadata};
5use ormlitex_core::config::get_var_model_folders;
6use crate::codegen::common::ormlitexCodegen;
7use proc_macro::TokenStream;
8use std::borrow::Borrow;
9
10use quote::quote;
11use lazy_static::lazy_static;
12use syn::{Data, DeriveInput, Item, ItemStruct, parse_macro_input};
13use ormlitex_attr::DeriveInputExt;
14use std::collections::HashMap;
15use std::ops::Deref;
16use once_cell::sync::OnceCell;
17use ormlitex_core::Error::ormlitexError;
18use codegen::into_arguments::impl_IntoArguments;
19use crate::codegen::from_row::{impl_from_row_using_aliases, impl_FromRow};
20use crate::codegen::insert::impl_InsertModel;
21use crate::codegen::insert_model::struct_InsertModel;
22use crate::codegen::join_description::static_join_descriptions;
23use crate::codegen::meta::{impl_JoinMeta, impl_TableMeta};
24use crate::codegen::model::{impl_HasModelBuilder, impl_Model};
25use crate::codegen::model_builder::{impl_ModelBuilder, struct_ModelBuilder};
26
27mod util;
28mod codegen;
29
30pub(crate) type MetadataCache = HashMap<String, ModelMetadata>;
31
32static TABLES: OnceCell<MetadataCache> = OnceCell::new();
33
34fn get_tables() -> &'static MetadataCache {
35    TABLES.get_or_init(|| load_metadata_cache())
36}
37
38fn load_metadata_cache() -> MetadataCache {
39    let mut tables = HashMap::new();
40    let paths = get_var_model_folders();
41    let paths = paths.iter().map(|p| p.as_path()).collect::<Vec<_>>();
42    let schema = schema_from_filepaths(&paths).expect("Failed to preload models");
43    for meta in schema.tables {
44        let name = meta.struct_name().to_string();
45        tables.insert(name, meta);
46    }
47    tables
48}
49
50/// For a given struct, determine what codegen to use.
51fn get_databases(table_meta: &TableMetadata) -> Vec<Box<dyn ormlitexCodegen>> {
52    let mut databases: Vec<Box<dyn ormlitexCodegen>> = Vec::new();
53    let dbs = table_meta.databases.clone();
54    if dbs.is_empty() {
55        #[cfg(feature = "default-sqlite")]
56        databases.push(Box::new(codegen::sqlite::SqliteBackend {}));
57        #[cfg(feature = "default-postgres")]
58        databases.push(Box::new(codegen::postgres::PostgresBackend {}));
59        #[cfg(feature = "default-mysql")]
60        databases.push(Box::new(codegen::mysql::MysqlBackend {}));
61    } else {
62        for db in dbs {
63            match db.as_str() {
64                #[cfg(feature = "sqlite")]
65                "sqlite" => databases.push(Box::new(codegen::sqlite::SqliteBackend {})),
66                #[cfg(feature = "postgres")]
67                "postgres" => databases.push(Box::new(codegen::postgres::PostgresBackend {})),
68                #[cfg(feature = "mysql")]
69                "mysql" => databases.push(Box::new(codegen::mysql::MysqlBackend {})),
70                "sqlite" | "postgres" | "mysql" => panic!("Database {} is not enabled. Enable it with features = [\"{}\"]", db, db),
71                _ => panic!("Unknown database: {}", db),
72            }
73        }
74    }
75    if databases.is_empty() {
76        let mut count = 0;
77        #[cfg(feature = "sqlite")]
78        {
79            count += 1;
80        }
81        #[cfg(feature = "postgres")]
82        {
83            count += 1;
84        }
85        #[cfg(feature = "mysql")]
86        {
87            count += 1;
88        }
89        if count > 1 {
90            panic!("You have more than one database configured using features, but no database is specified for this model. \
91            Specify a database for the model like this:\n\n#[ormlitex(database = \"<db>\")]\n\nOr you can enable \
92            a default database feature:\n\n # Cargo.toml\normlitex = {{ features = [\"default-<db>\"] }}");
93        }
94    }
95    if databases.is_empty() {
96        #[cfg(feature = "sqlite")]
97        databases.push(Box::new(codegen::sqlite::SqliteBackend {}));
98        #[cfg(feature = "postgres")]
99        databases.push(Box::new(codegen::postgres::PostgresBackend {}));
100        #[cfg(feature = "mysql")]
101        databases.push(Box::new(codegen::mysql::MysqlBackend {}));
102    }
103    databases
104}
105
106/// Derive macro for `#[derive(Model)]` It additionally generates FromRow for the struct, since
107/// Model requires FromRow.
108#[proc_macro_derive(Model, attributes(ormlitex))]
109pub fn expand_ormlitex_model(input: TokenStream) -> TokenStream {
110    let ast = parse_macro_input!(input as DeriveInput);
111    let Data::Struct(data) = &ast.data else { panic!("Only structs can derive Model"); };
112
113    let meta = ModelMetadata::from_derive(&ast).expect("Failed to parse table metadata");
114
115    let mut databases = get_databases(&meta.inner);
116    let tables = get_tables();
117
118    let first = databases.remove(0);
119
120    let primary = {
121        let db = first.as_ref();
122        let impl_TableMeta = impl_TableMeta(&meta.inner, Some(meta.pkey.column_name.as_str()));
123        let impl_JoinMeta = impl_JoinMeta(&meta);
124        let static_join_descriptions = static_join_descriptions(&meta.inner, &tables);
125        let impl_Model = impl_Model(db, &meta, tables);
126        let impl_HasModelBuilder = impl_HasModelBuilder(db, &meta);
127        let impl_FromRow = impl_FromRow(&meta.inner, &tables);
128        let impl_from_row_using_aliases = impl_from_row_using_aliases(&meta.inner, &tables);
129
130        let struct_ModelBuilder = struct_ModelBuilder(&ast, &meta);
131        let impl_ModelBuilder = impl_ModelBuilder(db, &meta);
132
133        let struct_InsertModel = struct_InsertModel(&ast, &meta);
134        let impl_InsertModel = impl_InsertModel(db, &meta);
135
136        quote! {
137            #impl_TableMeta
138            #impl_JoinMeta
139
140            #static_join_descriptions
141            #impl_Model
142            #impl_HasModelBuilder
143            #impl_FromRow
144            #impl_from_row_using_aliases
145
146            #struct_ModelBuilder
147            #impl_ModelBuilder
148
149            #struct_InsertModel
150            #impl_InsertModel
151        }
152    };
153
154    let rest = databases.iter().map(|db| {
155        let impl_Model = impl_Model(db.as_ref(), &meta, tables);
156        quote! {
157            #impl_Model
158        }
159    });
160
161    TokenStream::from(quote! {
162        #primary
163        #(#rest)*
164    })
165}
166
167#[proc_macro_derive(FromRow, attributes(ormlitex))]
168pub fn expand_derive_fromrow(input: TokenStream) -> TokenStream {
169    let ast = parse_macro_input!(input as DeriveInput);
170    let Data::Struct(data) = &ast.data else { panic!("Only structs can derive Model"); };
171
172    let meta = TableMetadata::from_derive(&ast).unwrap();
173
174    let databases = get_databases(&meta);
175    let tables = get_tables();
176
177    let expanded = databases.iter().map(|db| {
178        let impl_FromRow = impl_FromRow(&meta, &tables);
179        let impl_from_row_using_aliases = impl_from_row_using_aliases(&meta, &tables);
180        quote! {
181            #impl_FromRow
182            #impl_from_row_using_aliases
183        }
184    });
185
186    TokenStream::from(quote! {
187        #(#expanded)*
188    })
189}
190
191#[proc_macro_derive(TableMeta, attributes(ormlitex))]
192pub fn expand_derive_table_meta(input: TokenStream) -> TokenStream {
193    let ast = parse_macro_input!(input as DeriveInput);
194    let Data::Struct(data) = &ast.data else { panic!("Only structs can derive Model"); };
195
196    let table_meta = TableMetadata::from_derive(&ast).expect("Failed to parse table metadata");
197    let databases = get_databases(&table_meta);
198    let db = databases.first().expect("No database configured");
199
200    let impl_TableMeta = impl_TableMeta(&table_meta, table_meta.pkey.as_ref().map(|pkey| pkey.as_str()));
201    TokenStream::from(impl_TableMeta)
202}
203
204#[proc_macro_derive(IntoArguments, attributes(ormlitex))]
205pub fn expand_derive_into_arguments(input: TokenStream) -> TokenStream {
206    let ast = parse_macro_input!(input as DeriveInput);
207    let Data::Struct(data) = &ast.data else { panic!("Only structs can derive Model"); };
208
209    let meta = TableMetadata::from_derive(&ast).unwrap();
210    let databases = get_databases(&meta);
211
212    let expanded = databases.iter().map(|db| {
213        let impl_IntoArguments = impl_IntoArguments(db.as_ref(), &meta);
214        impl_IntoArguments
215    });
216    TokenStream::from(quote! {
217        #(#expanded)*
218    })
219}
220
221/// This is a no-op marker trait that allows the migration tool to know when a user has
222/// manually implemented a type.
223///
224/// This is useful for having data that's a string in the database, but a strum::EnumString in code.
225#[proc_macro_derive(ManualType)]
226pub fn expand_derive_manual_type(input: TokenStream) -> TokenStream {
227    TokenStream::new()
228}