lib.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. use proc_macro::TokenStream;
  2. use quote::{format_ident, quote};
  3. use syn::{parse_macro_input, DeriveInput};
  4. use convert_case::{Case, Casing};
  5. fn parse_microrm_ref(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
  6. for attr in attrs {
  7. if attr.path.segments.is_empty() {
  8. continue;
  9. }
  10. if attr.tokens.is_empty() && attr.path.segments.last().unwrap().ident == "microrm_internal"
  11. {
  12. return quote! { crate };
  13. }
  14. }
  15. quote! { ::microrm }
  16. }
  17. fn parse_fk(attrs: &[syn::Attribute]) -> bool {
  18. for attr in attrs {
  19. if attr.path.segments.len() == 1
  20. && attr.path.segments.last().unwrap().ident == "microrm_foreign"
  21. {
  22. return true;
  23. }
  24. }
  25. false
  26. }
  27. /// Turns a serializable/deserializable struct into a microrm entity model.
  28. ///
  29. /// There are two important visible effects:
  30. /// - Provides an implementation of `microrm::model::Entity`
  31. /// - Defines a <struct-name>Columns enum
  32. ///
  33. /// Note that names are converted from CamelCase to snake_case and vice versa
  34. /// where applicable, so a struct named `TestModel` is given a table name `test_model`
  35. /// and a struct field named `field_name` is given a variant name of `FieldName`.
  36. ///
  37. /// The `#[microrm...]` attributes can be used to control the derivation somewhat.
  38. /// The following are understood for the Entity struct:
  39. /// - `#[microrm_internal]`: this is internal to the microrm crate (of extremely limited usefulness
  40. /// outside the microrm library)
  41. ///
  42. /// The following are understood on individual fields:
  43. /// - `#[microrm_foreign]`: this is a foreign key (and the field must be of a type implementing `EntityID`)
  44. #[proc_macro_derive(Entity, attributes(microrm_internal, microrm_foreign))]
  45. pub fn derive_entity(tokens: TokenStream) -> TokenStream {
  46. let input = parse_macro_input!(tokens as DeriveInput);
  47. let microrm_ref = parse_microrm_ref(&input.attrs);
  48. let struct_name = &input.ident;
  49. let enum_name = format_ident!("{}Columns", &input.ident);
  50. let id_name = format_ident!("{}ID", &input.ident);
  51. let table_name = format!("{}", struct_name).to_case(Case::Snake);
  52. let st = match input.data {
  53. syn::Data::Struct(st) => st,
  54. _ => panic!("Can only use derive(Entity) on structs!"),
  55. };
  56. let fields = match st.fields {
  57. syn::Fields::Named(fields) => fields,
  58. _ => panic!("Can only use derive(Entity) on non-unit structs with named fields!"),
  59. };
  60. let mut variants = Vec::new();
  61. let mut field_names = Vec::new();
  62. let mut field_numbers = Vec::new();
  63. let mut field_types = Vec::new();
  64. let mut value_references = Vec::new();
  65. let mut foreign_keys = Vec::new();
  66. let mut foreign_key_impls = Vec::new();
  67. for name in fields.named.iter() {
  68. let converted_case =
  69. format!("{}", name.ident.as_ref().unwrap().clone()).to_case(Case::UpperCamel);
  70. let converted_case = format_ident!("{}", converted_case);
  71. variants.push(converted_case.clone());
  72. let field_name = name.ident.as_ref().unwrap().clone();
  73. let field_name_str = format!("{}", field_name);
  74. field_names.push(quote! { Self::Column::#converted_case => #field_name_str });
  75. let nn = field_numbers.len() + 1;
  76. field_numbers.push(quote! { #nn => Self::#converted_case, });
  77. let ty = &name.ty;
  78. field_types.push(quote!{ <#ty as #microrm_ref::model::Modelable>::column_type() });
  79. if parse_fk(&name.attrs) {
  80. let fk_struct_name = format_ident!("{}{}ForeignKey", struct_name, converted_case);
  81. let ty = &name.ty;
  82. foreign_keys.push(quote! {
  83. &#fk_struct_name { col: #enum_name::#converted_case }
  84. });
  85. foreign_key_impls.push(quote!{
  86. struct #fk_struct_name {
  87. col: #enum_name
  88. }
  89. impl #microrm_ref::model::EntityForeignKey<#enum_name> for #fk_struct_name {
  90. fn local_column(&self) -> &#enum_name { &self.col }
  91. fn foreign_table_name(&self) -> &'static str {
  92. <<#ty as #microrm_ref::model::EntityID>::Entity as #microrm_ref::model::Entity>::table_name()
  93. }
  94. fn foreign_column_name(&self) -> &'static str {
  95. "id"
  96. }
  97. }
  98. });
  99. }
  100. value_references.push(quote! { &self. #field_name });
  101. }
  102. let column_types_name = format_ident!("{}_COLUMN_TYPES", struct_name.to_string().to_case(Case::ScreamingSnake));
  103. let field_count = fields.named.iter().count();
  104. quote!{
  105. // Related types for #struct_name
  106. #[derive(Clone,Copy,PartialEq,Hash)]
  107. #[allow(unused)]
  108. #[repr(usize)]
  109. pub enum #enum_name {
  110. ID,
  111. #(#variants),*
  112. }
  113. #[derive(Debug,PartialEq,Clone,Copy,#microrm_ref::re_export::serde::Serialize,#microrm_ref::re_export::serde::Deserialize)]
  114. #[allow(unused)]
  115. pub struct #id_name (i64);
  116. // Implementations for related types
  117. impl #microrm_ref::model::EntityColumns for #enum_name {
  118. type Entity = #struct_name;
  119. }
  120. impl std::convert::From<usize> for #enum_name {
  121. fn from(i: usize) -> Self {
  122. match i {
  123. 0 => Self::ID,
  124. #(#field_numbers)*
  125. _ => {
  126. panic!("Given invalid usize to convert to column")
  127. },
  128. }
  129. }
  130. }
  131. impl #microrm_ref::model::EntityID for #id_name {
  132. type Entity = #struct_name;
  133. fn from_raw_id(raw: i64) -> Self { Self(raw) }
  134. fn raw_id(&self) -> i64 { self.0 }
  135. }
  136. impl #microrm_ref::model::Modelable for #id_name {
  137. fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
  138. use #microrm_ref::re_export::sqlite::Bindable;
  139. self.0.bind(stmt, col)
  140. }
  141. fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self, usize)> where Self: Sized {
  142. stmt.read::<i64>(col_offset).map(|x| (#id_name(x), 1))
  143. }
  144. fn column_type() -> &'static str where Self: Sized {
  145. "integer"
  146. }
  147. }
  148. #microrm_ref::re_export::lazy_static::lazy_static!{
  149. static ref #column_types_name: [&'static str;#field_count + 1] = {
  150. [
  151. "id",
  152. #(#field_types),*
  153. ]
  154. };
  155. }
  156. // Implementations for #struct_name
  157. impl #microrm_ref::model::Entity for #struct_name {
  158. type Column = #enum_name;
  159. type ID = #id_name;
  160. fn table_name() -> &'static str { #table_name }
  161. fn column_count() -> usize {
  162. // +1 for ID column
  163. #field_count + 1
  164. }
  165. fn index(c: Self::Column) -> usize {
  166. c as usize
  167. }
  168. fn name(c: Self::Column) -> &'static str {
  169. match c {
  170. Self::Column::ID => "ID",
  171. #(#field_names),*
  172. }
  173. }
  174. fn values(&self) -> Vec<&dyn #microrm_ref::model::Modelable> {
  175. vec![ #(#value_references),* ]
  176. }
  177. fn column_types() -> &'static [&'static str] {
  178. #column_types_name.as_ref()
  179. }
  180. fn foreign_keys() -> &'static [&'static dyn #microrm_ref::model::EntityForeignKey<Self::Column>] {
  181. &[#(#foreign_keys),*]
  182. }
  183. }
  184. // Foreign key struct implementations
  185. #(#foreign_key_impls)*
  186. }.into()
  187. }
  188. /// Marks a struct or enum as able to be directly used in an Entity to correspond to a single database column.
  189. #[proc_macro_derive(Modelable, attributes(microrm_internal))]
  190. pub fn derive_modelable(tokens: TokenStream) -> TokenStream {
  191. let input = parse_macro_input!(tokens as DeriveInput);
  192. if let syn::Data::Enum(e) = &input.data {
  193. return derive_modelable_enum(&input, e)
  194. }
  195. if let syn::Data::Struct(s) = &input.data {
  196. if s.fields.len() == 1 {
  197. return derive_transparent_struct(&input, s)
  198. }
  199. }
  200. derive_modelable_general(&input)
  201. }
  202. fn derive_transparent_struct(input: &syn::DeriveInput, ds: &syn::DataStruct) -> TokenStream {
  203. // for single-element structs, we can simply store these transparently however the element
  204. // would be stored
  205. let microrm_ref = parse_microrm_ref(&input.attrs);
  206. let struct_name = &input.ident;
  207. let field = ds.fields.iter().next().unwrap();
  208. let (bind_to, build_from) =
  209. if let Some(i) = &field.ident {
  210. (
  211. quote!{ self.#i.bind_to(stmt, col) },
  212. quote!{ Self { #i: field.0 } }
  213. )
  214. }
  215. else {
  216. (
  217. quote!{ self.0.bind_to(stmt, col) },
  218. quote!{ Self(field.0) }
  219. )
  220. };
  221. let field_ty = &field.ty;
  222. quote! {
  223. impl #microrm_ref::model::Modelable for #struct_name {
  224. fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
  225. #bind_to
  226. }
  227. fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
  228. let field = #field_ty::build_from(stmt, col_offset)?;
  229. Ok((#build_from, field.1))
  230. }
  231. fn column_type() -> &'static str where Self: Sized {
  232. <#field_ty as #microrm_ref::model::Modelable>::column_type()
  233. }
  234. }
  235. }.into()
  236. }
  237. fn derive_modelable_enum(input: &syn::DeriveInput, de: &syn::DataEnum) -> TokenStream {
  238. for variant in &de.variants {
  239. if !variant.fields.is_empty() {
  240. return derive_modelable_general(input)
  241. }
  242. }
  243. // only unit variants! we can store as a string
  244. let microrm_ref = parse_microrm_ref(&input.attrs);
  245. let enum_name = &input.ident;
  246. let mut variant_names = Vec::new();
  247. let mut variant_name_strs = Vec::new();
  248. for variant in &de.variants {
  249. variant_names.push(variant.ident.clone());
  250. variant_name_strs.push(variant.ident.to_string());
  251. }
  252. quote! {
  253. impl #microrm_ref::model::Modelable for #enum_name {
  254. fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
  255. match self {
  256. #(
  257. Self::#variant_names => #variant_name_strs.bind_to(stmt, col)
  258. ),*
  259. }
  260. }
  261. fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
  262. let str_form = String::build_from(stmt, col_offset)?.0;
  263. #(
  264. if str_form == #variant_name_strs { return Ok((Self::#variant_names, 1)) }
  265. )*
  266. return Err(#microrm_ref::re_export::sqlite::Error { code: None, message: None })
  267. }
  268. fn column_type() -> &'static str where Self: Sized {
  269. "text"
  270. }
  271. }
  272. }.into()
  273. }
  274. fn derive_modelable_general(input: &syn::DeriveInput) -> TokenStream {
  275. let microrm_ref = parse_microrm_ref(&input.attrs);
  276. let ident = &input.ident;
  277. quote!{
  278. impl #microrm_ref::model::Modelable for #ident {
  279. fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
  280. use #microrm_ref::re_export::sqlite;
  281. use #microrm_ref::model::Modelable;
  282. serde_json::to_string(self).expect("can be serialized").bind_to(stmt, col)
  283. }
  284. fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
  285. use #microrm_ref::re_export::sqlite;
  286. use #microrm_ref::model::Modelable;
  287. let str_data = stmt.read::<String>(col_offset).map_err(|e| sqlite::Error { code: None, message: Some(e.to_string()) })?;
  288. let data = serde_json::from_str(str_data.as_str()).map_err(|e| sqlite::Error { code: None, message: Some(e.to_string()) })?;
  289. Ok((data,1))
  290. }
  291. fn column_type() -> &'static str where Self: Sized {
  292. "blob"
  293. }
  294. }
  295. }.into()
  296. }
  297. type ColumnList = syn::punctuated::Punctuated<syn::TypePath, syn::Token![,]>;
  298. struct MakeIndexParams {
  299. unique: Option<syn::Token![!]>,
  300. name: syn::Ident,
  301. #[allow(dead_code)]
  302. comma: syn::Token![,],
  303. columns: ColumnList,
  304. }
  305. impl syn::parse::Parse for MakeIndexParams {
  306. fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
  307. Ok(Self {
  308. unique: input.parse()?,
  309. name: input.parse()?,
  310. comma: input.parse()?,
  311. columns: ColumnList::parse_separated_nonempty(input)?,
  312. })
  313. }
  314. }
  315. fn do_make_index(tokens: TokenStream, microrm_ref: proc_macro2::TokenStream) -> TokenStream {
  316. let input = parse_macro_input!(tokens as MakeIndexParams);
  317. let index_struct_name = input.name;
  318. let first_col = input.columns.first().unwrap();
  319. let mut column_type_path = first_col.path.clone();
  320. // remove variant name
  321. column_type_path.segments.pop();
  322. let last = column_type_path
  323. .segments
  324. .pop()
  325. .expect("Full path to EntityColumn variant");
  326. column_type_path.segments.push(last.value().clone());
  327. let index_entity_type_name = format_ident!("{}Entity", index_struct_name);
  328. let columns = input.columns.clone().into_iter();
  329. let index_sql_name = format!("{}", index_struct_name).to_case(Case::Snake);
  330. let unique = input.unique.is_some();
  331. quote!{
  332. pub struct #index_struct_name {}
  333. type #index_entity_type_name = <#column_type_path as #microrm_ref::model::EntityColumns>::Entity;
  334. impl #microrm_ref::model::Index for #index_struct_name {
  335. type IndexedEntity = #index_entity_type_name;
  336. fn index_name() -> &'static str {
  337. #index_sql_name
  338. }
  339. fn columns() -> &'static [#column_type_path] where Self: Sized {
  340. &[#(#columns),*]
  341. }
  342. fn unique() -> bool where Self: Sized {
  343. #unique
  344. }
  345. }
  346. }.into()
  347. }
  348. /// Defines a struct to represent a optionally-unique index on a table.
  349. ///
  350. /// Suppose the following `Entity` definition is used:
  351. ///
  352. /// ```ignore
  353. /// #[derive(Entity,Serialize,Deserialize)]
  354. /// struct SystemUser {
  355. /// username: String,
  356. /// hashed_password: String
  357. /// }
  358. /// ```
  359. ///
  360. /// We can now use `make_index!` to define an index on the username field:
  361. /// ```ignore
  362. /// make_index!(SystemUsernameIndex, SystemUserColumns::Username)
  363. /// ```
  364. ///
  365. /// This index can be made unique by adding a `!` prior to the type name, as:
  366. /// ```ignore
  367. /// make_index!(!SystemUsernameUniqueIndex, SystemUserColumns::Username)
  368. /// ```
  369. #[proc_macro]
  370. pub fn make_index(tokens: TokenStream) -> TokenStream {
  371. do_make_index(tokens, quote! { ::microrm })
  372. }
  373. /// For internal use inside the microrm library. See `make_index`.
  374. #[proc_macro]
  375. pub fn make_index_internal(tokens: TokenStream) -> TokenStream {
  376. do_make_index(tokens, quote! { crate })
  377. }
  378. // , attributes(microrm_internal))]