123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- use proc_macro::TokenStream;
- use quote::{format_ident, quote};
- use syn::{parse_macro_input, DeriveInput};
- use convert_case::{Case, Casing};
- fn parse_microrm_ref(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
- for attr in attrs {
- if attr.path.segments.is_empty() {
- continue;
- }
- if attr.tokens.is_empty() && attr.path.segments.last().unwrap().ident == "microrm_internal"
- {
- return quote! { crate };
- }
- }
- quote! { ::microrm }
- }
- fn parse_fk(attrs: &[syn::Attribute]) -> bool {
- for attr in attrs {
- if attr.path.segments.len() == 1
- && attr.path.segments.last().unwrap().ident == "microrm_foreign"
- {
- return true;
- }
- }
- false
- }
- pub fn derive_entity(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as DeriveInput);
- let microrm_ref = parse_microrm_ref(&input.attrs);
- let struct_name = &input.ident;
- let enum_name = format_ident!("{}Columns", &input.ident);
- let id_name = format_ident!("{}ID", &input.ident);
- let table_name = format!("{}", struct_name).to_case(Case::Snake);
- let st = match input.data {
- syn::Data::Struct(st) => st,
- _ => panic!("Can only use derive(Entity) on structs!"),
- };
- let fields = match st.fields {
- syn::Fields::Named(fields) => fields,
- _ => panic!("Can only use derive(Entity) on non-unit structs with named fields!"),
- };
- let mut variants = Vec::new();
- let mut field_names = Vec::new();
- let mut field_numbers = Vec::new();
- let mut field_types = Vec::new();
- let mut value_references = Vec::new();
- let mut foreign_keys = Vec::new();
- let mut foreign_key_impls = Vec::new();
- for name in fields.named.iter() {
- let converted_case =
- format!("{}", name.ident.as_ref().unwrap().clone()).to_case(Case::UpperCamel);
- let converted_case = format_ident!("{}", converted_case);
- variants.push(converted_case.clone());
- let field_name = name.ident.as_ref().unwrap().clone();
- let field_name_str = format!("{}", field_name);
- field_names.push(quote! { Self::Column::
- let nn = field_numbers.len() + 1;
- field_numbers.push(quote! {
- let ty = &name.ty;
- field_types.push(quote!{ <
- if parse_fk(&name.attrs) {
- let fk_struct_name = format_ident!("{}{}ForeignKey", struct_name, converted_case);
- let ty = &name.ty;
- foreign_keys.push(quote! {
- &
- });
- foreign_key_impls.push(quote!{
- struct
- col:
- }
- impl
- fn local_column(&self) -> &
- fn foreign_table_name(&self) -> &'static str {
- <<#ty as #microrm_ref::model::EntityID>::Entity as #microrm_ref::model::Entity>::table_name()
- }
- fn foreign_column_name(&self) -> &'static str {
- "id"
- }
- }
- });
- }
- value_references.push(quote! { &self.
- }
- let column_types_name = format_ident!("{}_COLUMN_TYPES", struct_name.to_string().to_case(Case::ScreamingSnake));
- let field_count = fields.named.iter().count();
- quote!{
-
-
-
-
- pub enum
- ID,
-
- }
-
-
- pub struct
-
- impl
- type Entity =
- }
- impl std::convert::From<usize> for
- fn from(i: usize) -> Self {
- match i {
- 0 => Self::ID,
-
- _ => {
- panic!("Given invalid usize to convert to column")
- },
- }
- }
- }
- impl
- type Entity =
- fn from_raw_id(raw: i64) -> Self { Self(raw) }
- fn raw_id(&self) -> i64 { self.0 }
- }
- impl
- fn bind_to(&self, stmt: &mut
- use #microrm_ref::re_export::sqlite::Bindable;
- self.0.bind(stmt, col)
- }
- fn build_from(stmt: &
- stmt.read::<i64>(col_offset).map(|x| (
- }
- fn column_type() -> &'static str where Self: Sized {
- "integer"
- }
- }
- #microrm_ref::re_export::lazy_static::lazy_static!{
- static ref #column_types_name: [&'static str;
- [
- "id",
-
- ]
- };
- }
-
- impl
- type Column =
- type ID =
- fn table_name() -> &'static str { #table_name }
- fn column_count() -> usize {
- // +1 for ID column
- #field_count + 1
- }
- fn index(c: Self::Column) -> usize {
- c as usize
- }
- fn name(c: Self::Column) -> &'static str {
- match c {
- Self::Column::ID => "ID",
-
- }
- }
- fn values(&self) -> Vec<&dyn
- vec![
- }
- fn column_types() -> &'static [&'static str] {
-
- }
- fn foreign_keys() -> &'static [&'static dyn
- &[
- }
- }
-
-
- }.into()
- }
- pub fn derive_modelable(tokens: TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as DeriveInput);
- if let syn::Data::Enum(e) = &input.data {
- return derive_modelable_enum(&input, e)
- }
- if let syn::Data::Struct(s) = &input.data {
- if s.fields.len() == 1 {
- return derive_transparent_struct(&input, s)
- }
- }
- derive_modelable_general(&input)
- }
- fn derive_transparent_struct(input: &syn::DeriveInput, ds: &syn::DataStruct) -> TokenStream {
-
-
- let microrm_ref = parse_microrm_ref(&input.attrs);
- let struct_name = &input.ident;
- let field = ds.fields.iter().next().unwrap();
- let (bind_to, build_from) =
- if let Some(i) = &field.ident {
- (
- quote!{ self.
- quote!{ Self {
- )
- }
- else {
- (
- quote!{ self.0.bind_to(stmt, col) },
- quote!{ Self(field.0) }
- )
- };
- let field_ty = &field.ty;
- quote! {
- impl
- fn bind_to(&self, stmt: &mut
-
- }
- fn build_from(stmt: &
- let field =
- Ok((
- }
- fn column_type() -> &'static str where Self: Sized {
- <#field_ty as #microrm_ref::model::Modelable>::column_type()
- }
- }
- }.into()
- }
- fn derive_modelable_enum(input: &syn::DeriveInput, de: &syn::DataEnum) -> TokenStream {
- for variant in &de.variants {
- if !variant.fields.is_empty() {
- return derive_modelable_general(input)
- }
- }
- // only unit variants! we can store as a string
- let microrm_ref = parse_microrm_ref(&input.attrs);
- let enum_name = &input.ident;
- let mut variant_names = Vec::new();
- let mut variant_name_strs = Vec::new();
- for variant in &de.variants {
- variant_names.push(variant.ident.clone());
- variant_name_strs.push(variant.ident.to_string());
- }
- quote! {
- impl #microrm_ref::model::Modelable for #enum_name {
- fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
- match self {
- #(
- Self::#variant_names => #variant_name_strs.bind_to(stmt, col)
- ),*
- }
- }
- fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
- let str_form = String::build_from(stmt, col_offset)?.0;
- #(
- if str_form == #variant_name_strs { return Ok((Self::#variant_names, 1)) }
- )*
- return Err(#microrm_ref::re_export::sqlite::Error { code: None, message: None })
- }
- fn column_type() -> &'static str where Self: Sized {
- "text"
- }
- }
- }.into()
- }
- fn derive_modelable_general(input: &syn::DeriveInput) -> TokenStream {
- let microrm_ref = parse_microrm_ref(&input.attrs);
- let ident = &input.ident;
- quote!{
- impl
- fn bind_to(&self, stmt: &mut
- use #microrm_ref::re_export::sqlite;
- use #microrm_ref::model::Modelable;
- serde_json::to_string(self).expect("can be serialized").bind_to(stmt, col)
- }
- fn build_from(stmt: &
- use #microrm_ref::re_export::sqlite;
- use #microrm_ref::model::Modelable;
- let str_data = stmt.read::<String>(col_offset).map_err(|e| sqlite::Error { code: None, message: Some(e.to_string()) })?;
- let data = serde_json::from_str(str_data.as_str()).map_err(|e| sqlite::Error { code: None, message: Some(e.to_string()) })?;
- Ok((data,1))
- }
- fn column_type() -> &'static str where Self: Sized {
- "blob"
- }
- }
- }.into()
- }
- type ColumnList = syn::punctuated::Punctuated<syn::TypePath, syn::Token![,]>;
- struct MakeIndexParams {
- unique: Option<syn::Token![!]>,
- name: syn::Ident,
- #[allow(dead_code)]
- comma: syn::Token![,],
- columns: ColumnList,
- }
- impl syn::parse::Parse for MakeIndexParams {
- fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
- Ok(Self {
- unique: input.parse()?,
- name: input.parse()?,
- comma: input.parse()?,
- columns: ColumnList::parse_separated_nonempty(input)?,
- })
- }
- }
- fn do_make_index(tokens: TokenStream, microrm_ref: proc_macro2::TokenStream) -> TokenStream {
- let input = parse_macro_input!(tokens as MakeIndexParams);
- let index_struct_name = input.name;
- let first_col = input.columns.first().unwrap();
- let mut column_type_path = first_col.path.clone();
- // remove variant name
- column_type_path.segments.pop();
- let last = column_type_path
- .segments
- .pop()
- .expect("Full path to EntityColumn variant");
- column_type_path.segments.push(last.value().clone());
- let index_entity_type_name = format_ident!("{}Entity", index_struct_name);
- let columns = input.columns.clone().into_iter();
- let index_sql_name = format!("{}", index_struct_name).to_case(Case::Snake);
- let unique = input.unique.is_some();
- quote!{
- pub struct #index_struct_name {}
- type #index_entity_type_name = <#column_type_path as #microrm_ref::model::EntityColumns>::Entity;
- impl #microrm_ref::model::Index for #index_struct_name {
- type IndexedEntity = #index_entity_type_name;
- fn index_name() -> &'static str {
-
- }
- fn columns() -> &'static [#column_type_path] where Self: Sized {
- &[#(#columns),*]
- }
- fn unique() -> bool where Self: Sized {
- #unique
- }
- }
- }.into()
- }
- /// Defines a struct to represent a optionally-unique index on a table.
- ///
- /// Suppose the following `Entity` definition is used:
- ///
- /// ```ignore
- /// #[derive(Entity,Serialize,Deserialize)]
- /// struct SystemUser {
- /// username: String,
- /// hashed_password: String
- /// }
- /// ```
- ///
- /// We can now use `make_index!` to define an index on the username field:
- /// ```ignore
- /// make_index!(SystemUsernameIndex, SystemUserColumns::Username)
- /// ```
- ///
- /// This index can be made unique by adding a `!` prior to the type name, as:
- /// ```ignore
- /// make_index!(!SystemUsernameUniqueIndex, SystemUserColumns::Username)
- /// ```
- #[proc_macro]
- pub fn make_index(tokens: TokenStream) -> TokenStream {
- do_make_index(tokens, quote! { ::microrm })
- }
- /// For internal use inside the microrm library. See `make_index`.
- #[proc_macro]
- pub fn make_index_internal(tokens: TokenStream) -> TokenStream {
- do_make_index(tokens, quote! { crate })
- }
- // , attributes(microrm_internal))]
|