odbc_futures_derive/
lib.rs

1use inflector::Inflector;
2use quote::quote;
3use syn::Attribute;
4use synstructure::decl_derive;
5
6fn odbc_columns_derive(mut s: synstructure::Structure) -> proc_macro2::TokenStream {
7    const ODBC_STRING_UTF8: &'static str = "odbc_string_utf8";
8
9    let mut struct_use_utf8 = false;
10    let mut seq_col_number = 0u16;
11    let mut caseconv: Option<fn(&String) -> String> = None;
12
13    for struct_meta in s
14        .ast()
15        .attrs
16        .iter()
17        .map(Attribute::parse_meta)
18        .filter_map(Result::ok)
19    {
20        if let syn::Meta::Word(name) = &struct_meta {
21            let name = name.to_string();
22            match name.as_str() {
23                ODBC_STRING_UTF8 => struct_use_utf8 = true,
24                "odbc_columns_seq" => {
25                    seq_col_number = 1;
26                }
27                "odbc_rename_camel_case" => caseconv = Some(Inflector::to_camel_case),
28                "odbc_rename_class_case" => caseconv = Some(Inflector::to_class_case),
29                "odbc_rename_kebab_case" => caseconv = Some(Inflector::to_kebab_case),
30                "odbc_rename_train_case" => caseconv = Some(Inflector::to_train_case),
31                "odbc_rename_screaming_snake_case" => {
32                    caseconv = Some(Inflector::to_screaming_snake_case)
33                }
34                "odbc_rename_table_case" => caseconv = Some(Inflector::to_table_case),
35                "odbc_rename_sentence_case" => caseconv = Some(Inflector::to_sentence_case),
36                "odbc_rename_snake_case" => caseconv = Some(Inflector::to_snake_case),
37                "odbc_rename_pascal_case" => caseconv = Some(Inflector::to_pascal_case),
38                "odbc_rename_foreign_key" => caseconv = Some(Inflector::to_foreign_key),
39                "odbc_rename_pluralize" => caseconv = Some(Inflector::to_plural),
40                "odbc_rename_singularize" => caseconv = Some(Inflector::to_singular),
41                _ => {}
42            }
43        }
44    }
45
46    s.filter(|bi|{
47        for field_meta in bi.ast().attrs.iter().map(Attribute::parse_meta).filter_map(Result::ok) {
48            if let syn::Meta::Word(name) = &field_meta {
49                if name == "odbc_ignore" {
50                    return false;
51                }
52            }
53        }
54        true
55    });
56
57    let body = s.bind_with(|_| synstructure::BindStyle::RefMut).each(|bi| {
58        let field = bi.ast();
59
60        let mut field_use_utf8 = false;
61        let mut field_force_use_utf16 = false;
62        let mut use_time2: Option<bool> = None;
63
64        let mut col_name = field.ident.as_ref().map(ToString::to_string);
65        if let Some(caseconv) = caseconv {
66            col_name = col_name.as_ref().map(caseconv);
67        }
68
69        let mut col_number: Option<u16> = None;
70        let mut visit_nested = false;
71
72        for field_meta in field
73            .attrs
74            .iter()
75            .map(Attribute::parse_meta)
76            .filter_map(Result::ok)
77        {
78            match &field_meta {
79                syn::Meta::Word(name) if name == ODBC_STRING_UTF8 => field_use_utf8 = true,
80                syn::Meta::Word(name) if name == "odbc_string_utf16" => field_force_use_utf16 = true,
81                syn::Meta::Word(name) if name == "odbc_nested" => {
82                    visit_nested = true;
83                    break;
84                }
85                syn::Meta::NameValue(name_value) if name_value.ident == "odbc_time" => {
86                    match &name_value.lit {
87                        syn::Lit::Str(name) if name.value() == "time" => use_time2 = Some(false),
88                        syn::Lit::Str(name) if name.value() == "time2" => use_time2 = Some(true),
89                        x => panic!("not a valid odbc_time name {:?}", x),
90                    }
91                }
92                syn::Meta::NameValue(name_value) if name_value.ident == "odbc_col_name" => {
93                    match &name_value.lit {
94                        syn::Lit::Str(name) => col_name = Some(name.value()),
95                        x => panic!("not a valid column name {:?}", x),
96                    }
97                }
98                syn::Meta::NameValue(name_value) if name_value.ident == "odbc_col_number" => {
99                    match &name_value.lit {
100                        syn::Lit::Int(number) => {
101                            let val = number.value();
102                            assert!(val > 0, "column numbers start at 1");
103                            assert!(val <= std::u16::MAX as u64);
104                            col_number = Some(val as u16);
105                        }
106                        x => panic!("not a valid column number {:?}", x),
107                    }
108                }
109                _ => {}
110            }
111        }
112
113        if visit_nested {
114            return quote! {
115                if let Some(poll) = #bi.visit(visitor) {
116                    return Some(poll);
117                }
118            }
119        }
120
121        assert!(!field_force_use_utf16 || !field_use_utf8);
122
123        let ty = &field.ty;
124        let field_type: String = quote!(#ty).to_string();
125        let use_string_context = field_type == "String"
126            || field_type == "Option < String >"
127            || field_use_utf8
128            || field_force_use_utf16;
129
130        let context_type = if use_string_context {
131            if (struct_use_utf8 || field_use_utf8) && !field_force_use_utf16 {
132                quote!(())
133            } else {
134                quote!(Vec<u16>)
135            }
136        } else if let Some(time2) = use_time2 {
137          if time2 {
138              quote!(odbc_sys::SQL_SS_TIME2_STRUCT)
139          } else {
140              quote!(odbc_sys::SQL_TIME_STRUCT)
141          }
142        } else {
143            quote!(_)
144        };
145
146        let accept = quote! {
147            return Some( visitor.accept::< _, #context_type >(#bi) );
148        };
149
150        if let Some(col_number) = col_number {
151            quote! {
152                if visitor.col_number() == #col_number {
153                    #accept
154                }
155            }
156        } else if seq_col_number > 0 {
157            let val = seq_col_number;
158            seq_col_number += 1;
159            quote! {
160                if visitor.col_number() == #val {
161                    #accept
162                }
163            }
164        } else if let Some(col_name) = col_name {
165            quote! {
166                if visitor.col_name() == #col_name {
167                    #accept
168                }
169            }
170        } else {
171            panic!("failed to setup binding for field {:?}", field)
172        }
173    });
174
175    let in_self = std::env::var("CARGO_PKG_NAME").unwrap() == "odbc-futures";
176
177    let trait_name = if in_self {
178        quote!(crate::SqlColumns)
179    } else {
180        quote!(odbc_futures::SqlColumns)
181    };
182    let crate_name = if in_self {
183        quote!(crate)
184    } else {
185        quote!(odbc_futures)
186    };
187
188    let mut stream = s.bound_impl(trait_name, quote! {
189        fn visit(&mut self, visitor: &mut #crate_name::SqlColumnVisitor) -> Option< #crate_name::SqlPoll > {
190            extern crate futures;
191            extern crate odbc_sys;
192
193            match *self { #body };
194            None
195        }
196    });
197    if in_self {
198        let string = stream.to_string().replace("extern crate crate ;", "");
199        use std::str::FromStr;
200        stream = proc_macro2::TokenStream::from_str(&string).unwrap();
201    }
202    //eprintln!("{}", synstructure::unpretty_print(&stream));
203    stream
204}
205
206decl_derive!(
207    [Odbc, attributes(
208    odbc_string_utf8,
209    odbc_columns_seq,
210    odbc_rename_camel_case,
211    odbc_rename_class_case,
212    odbc_rename_kebab_case,
213    odbc_rename_train_case,
214    odbc_rename_screaming_snake_case,
215    odbc_rename_table_case,
216    odbc_rename_sentence_case,
217    odbc_rename_snake_case,
218    odbc_rename_pascal_case,
219    odbc_rename_foreign_key,
220    odbc_rename_pluralize,
221    odbc_rename_singularize,
222    odbc_string_utf16,
223    odbc_time,
224    odbc_nested,
225    odbc_ignore,
226    odbc_col_name,
227    odbc_col_number,
228    )] => odbc_columns_derive
229);