oracle_nosql_rust_sdk_derive/
lib.rs

1//
2// Copyright (c) 2024, 2025 Oracle and/or its affiliates. All rights reserved.
3//
4// Licensed under the Universal Permissive License v 1.0 as shown at
5//  https://oss.oracle.com/licenses/upl/
6//
7extern crate proc_macro;
8extern crate proc_macro2;
9extern crate syn;
10#[macro_use]
11extern crate quote;
12
13use proc_macro::TokenStream;
14use proc_macro2::{TokenStream as TokenStream2, TokenTree};
15use syn::{
16    parse::Parser, parse_macro_input, Data, DeriveInput, GenericArgument, Meta, PathArguments,
17    Type, TypePath,
18};
19
20/// Derive macro to specify a struct that can be written directly into, and read directly from, a
21/// NoSQL table row.
22///
23/// The single `nosql` attribute can be used to rename a field using the `column` key, and/or to specify
24/// its NoSQL Database field type using the `type` key (for example, from a Rust `i32` to a NoSQL `long`).
25///
26/// See the documentation of [`PutRequest::put()`](../struct.PutRequest.html#method.put) for
27/// example usage of this macro to put and get native structs to and from a NoSQL Database table.
28#[proc_macro_derive(NoSQLRow, attributes(nosql))]
29pub fn to_from_map_value(input: TokenStream) -> TokenStream {
30    // Parse input tokens into a syntax tree
31    let input = parse_macro_input!(input as DeriveInput);
32
33    // Build the trait implementation
34    impl_to_from_map_value(input)
35}
36
37fn impl_to_from_map_value(input: DeriveInput) -> TokenStream {
38    //println!(" impl_to_from_map_value: DeriveInput = {:#?}", input);
39    let name = &input.ident;
40    let name_string = name.to_string();
41
42    // check that input.data is Struct (vs Enum vs Union)
43    let ds;
44    if let Data::Struct(d) = input.data {
45        ds = d;
46    } else {
47        panic!("NoSQLRow only supports Struct datatypes");
48    }
49
50    #[derive(Debug)]
51    struct FieldNameType {
52        fname: String,
53        alias: Option<String>,
54        // TODO: ftype: String,
55    }
56
57    let mut fntypes: Vec<FieldNameType> = Vec::new();
58
59    for field in ds.fields {
60        // get column name from field name
61        // if "column" attribute given, use that
62
63        let mut alias: Option<String> = None;
64        for a in field.attrs {
65            let mut good: bool = false;
66            if let Meta::List(l) = a.meta {
67                for s in l.path.segments {
68                    if s.ident == "nosql" {
69                        good = true;
70                        break;
71                    }
72                }
73                if good == false {
74                    continue;
75                }
76                // we now have a "nosql" attribute list
77                // TODO
78                let mut is_column: bool = false;
79                for t in l.tokens {
80                    //println!(" token={:?}", t);
81                    match t {
82                        TokenTree::Ident(i) => {
83                            if is_column {
84                                alias = Some(i.to_string());
85                                break;
86                            }
87                            if i.to_string() == "column" {
88                                is_column = true;
89                            } else {
90                                is_column = false;
91                            }
92                        }
93                        //TokenTree::Punct(p) => println!("found a punct"),
94                        _ => (),
95                    }
96                }
97                if alias.is_some() {
98                    break;
99                }
100            }
101        }
102
103        let fname = if let Some(id) = field.ident {
104            id.to_string()
105        } else {
106            panic!("Field in NoSQLRow is missing ident");
107        };
108
109        // get field type, put in ftypes vector
110        // if "type" attribute given, use that
111        // otherwise, infer from rust type
112        let _ftype = if let Type::Path(p) = field.ty {
113            //ftypes.push(get_path_segment(&p, ""));
114            get_path_segment(&p, "")
115        } else {
116            panic!("Field type in NoSQLRow does not have Path element");
117        };
118        fntypes.push(FieldNameType { fname, alias });
119        //fntypes.push(FieldNameType{fname, alias, ftype});
120    }
121
122    //println!("fntypes: {:?}", fntypes);
123
124    let mut tbody = TokenStream2::default();
125    let mut fbody = TokenStream2::default();
126    for f in fntypes {
127        let fname = format_ident!("{}", f.fname);
128        let fnameq: String;
129        match f.alias {
130            Some(s) => fnameq = s,
131            None => fnameq = f.fname,
132        }
133        tbody.extend(quote! {
134            m.put(#fnameq, &self.#fname);
135        });
136        fbody.extend(quote! {
137            self.#fname = self.#fname.from_map(#fnameq, value)?;
138        });
139    }
140
141    let expanded = quote! {
142        impl NoSQLRow for #name {
143            fn to_map_value(&self) -> Result<MapValue, oracle_nosql_rust_sdk::NoSQLError> {
144                let mut m = MapValue::new();
145                #tbody
146                Ok(m)
147            }
148
149            fn from_map_value(&mut self, value: &MapValue) -> Result<(), oracle_nosql_rust_sdk::NoSQLError> {
150                #fbody
151                Ok(())
152            }
153        }
154
155        impl NoSQLColumnToFieldValue for #name {
156            fn to_field_value(&self) -> FieldValue {
157                let m = self.to_map_value();
158                if let Ok(mv) = m {
159                    return FieldValue::Map(mv);
160                }
161                // TODO: How to expose this error??
162                FieldValue::Null
163            }
164        }
165
166        impl NoSQLColumnFromFieldValue for #name {
167            fn from_field(fv: &FieldValue) -> Result<Self, oracle_nosql_rust_sdk::NoSQLError> {
168                if let FieldValue::Map(v) = fv {
169                    let mut s: #name = Default::default();
170                    s.from_map_value(v)?;
171                    return Ok(s);
172                }
173                Err(oracle_nosql_rust_sdk::NoSQLError::new(
174                    oracle_nosql_rust_sdk::NoSQLErrorCode::IllegalArgument,
175                    format!("NoSQL: Error converting field into {}: expected FieldValue::Map, actual: {:?}", #name_string, fv).as_str()))
176            }
177        }
178
179    };
180
181    //println!("expanded=\n{}\n", expanded);
182    // Return the generated impl
183    TokenStream::from(expanded)
184}
185
186fn get_path_segment(p: &TypePath, val: &str) -> String {
187    for elem in &p.path.segments {
188        let mut s = elem.ident.to_string();
189        //println!("gps: elem={}", s);
190        if let PathArguments::AngleBracketed(args) = &elem.arguments {
191            for a in &args.args {
192                if let GenericArgument::Type(tp) = a {
193                    if let Type::Path(p1) = tp {
194                        //println!("gps: recursing");
195                        s = val.to_string() + &s;
196                        return get_path_segment(&p1, &s);
197                    }
198                }
199            }
200            // TODO: error message?
201            //println!("gps: error");
202            return val.to_string();
203        }
204        // return after first element
205        s = val.to_string() + &s;
206        //println!("gps: returning {}", s);
207        return s.to_string();
208    }
209    //println!("gps: returning empty={}", val);
210    val.to_string()
211}
212
213/// (internal use only)
214#[proc_macro_attribute]
215pub fn add_planiter_fields(args: TokenStream, input: TokenStream) -> TokenStream {
216    let mut item_struct = parse_macro_input!(input as syn::ItemStruct);
217    let _ = parse_macro_input!(args as syn::parse::Nothing);
218
219    if let syn::Fields::Named(ref mut fields) = item_struct.fields {
220        //fields.named.push(syn::Field::parse_named.parse2(quote! { state: PlanIterState }).unwrap());
221        fields.named.push(
222            syn::Field::parse_named
223                .parse2(quote! { result_reg: i32 })
224                .unwrap(),
225        );
226        fields.named.push(
227            syn::Field::parse_named
228                .parse2(quote! { loc: Location })
229                .unwrap(),
230        );
231    }
232
233    return quote! {
234        #item_struct
235    }
236    .into();
237}