oracle_nosql_rust_sdk_derive/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
//
// Copyright (c) 2024, 2025 Oracle and/or its affiliates. All rights reserved.
//
// Licensed under the Universal Permissive License v 1.0 as shown at
//  https://oss.oracle.com/licenses/upl/
//
extern crate proc_macro;
extern crate proc_macro2;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use syn::{
    parse::Parser, parse_macro_input, Data, DeriveInput, GenericArgument, Meta, PathArguments,
    Type, TypePath,
};

/// Derive macro to specify a struct that can be written directly into, and read directly from, a
/// NoSQL table row.
///
/// The single `nosql` attribute can be used to rename a field using the `column` key, and/or to specify
/// its NoSQL Database field type using the `type` key (for example, from a Rust `i32` to a NoSQL `long`).
///
/// See the documentation of [`PutRequest::put()`](../struct.PutRequest.html#method.put) for
/// example usage of this macro to put and get native structs to and from a NoSQL Database table.
#[proc_macro_derive(NoSQLRow, attributes(nosql))]
pub fn to_from_map_value(input: TokenStream) -> TokenStream {
    // Parse input tokens into a syntax tree
    let input = parse_macro_input!(input as DeriveInput);

    // Build the trait implementation
    impl_to_from_map_value(input)
}

fn impl_to_from_map_value(input: DeriveInput) -> TokenStream {
    //println!(" impl_to_from_map_value: DeriveInput = {:#?}", input);
    let name = &input.ident;
    let name_string = name.to_string();

    // check that input.data is Struct (vs Enum vs Union)
    let ds;
    if let Data::Struct(d) = input.data {
        ds = d;
    } else {
        panic!("NoSQLRow only supports Struct datatypes");
    }

    #[derive(Debug)]
    struct FieldNameType {
        fname: String,
        alias: Option<String>,
        // TODO: ftype: String,
    }

    let mut fntypes: Vec<FieldNameType> = Vec::new();

    for field in ds.fields {
        // get column name from field name
        // if "column" attribute given, use that

        let mut alias: Option<String> = None;
        for a in field.attrs {
            let mut good: bool = false;
            if let Meta::List(l) = a.meta {
                for s in l.path.segments {
                    if s.ident == "nosql" {
                        good = true;
                        break;
                    }
                }
                if good == false {
                    continue;
                }
                // we now have a "nosql" attribute list
                // TODO
                let mut is_column: bool = false;
                for t in l.tokens {
                    //println!(" token={:?}", t);
                    match t {
                        TokenTree::Ident(i) => {
                            if is_column {
                                alias = Some(i.to_string());
                                break;
                            }
                            if i.to_string() == "column" {
                                is_column = true;
                            } else {
                                is_column = false;
                            }
                        }
                        //TokenTree::Punct(p) => println!("found a punct"),
                        _ => (),
                    }
                }
                if alias.is_some() {
                    break;
                }
            }
        }

        let fname = if let Some(id) = field.ident {
            id.to_string()
        } else {
            panic!("Field in NoSQLRow is missing ident");
        };

        // get field type, put in ftypes vector
        // if "type" attribute given, use that
        // otherwise, infer from rust type
        let _ftype = if let Type::Path(p) = field.ty {
            //ftypes.push(get_path_segment(&p, ""));
            get_path_segment(&p, "")
        } else {
            panic!("Field type in NoSQLRow does not have Path element");
        };
        fntypes.push(FieldNameType { fname, alias });
        //fntypes.push(FieldNameType{fname, alias, ftype});
    }

    //println!("fntypes: {:?}", fntypes);

    let mut tbody = TokenStream2::default();
    let mut fbody = TokenStream2::default();
    for f in fntypes {
        let fname = format_ident!("{}", f.fname);
        let fnameq: String;
        match f.alias {
            Some(s) => fnameq = s,
            None => fnameq = f.fname,
        }
        tbody.extend(quote! {
            m.put(#fnameq, &self.#fname);
        });
        fbody.extend(quote! {
            self.#fname = self.#fname.from_map(#fnameq, value)?;
        });
    }

    let expanded = quote! {
        impl NoSQLRow for #name {
            fn to_map_value(&self) -> Result<MapValue, oracle_nosql_rust_sdk::NoSQLError> {
                let mut m = MapValue::new();
                #tbody
                Ok(m)
            }

            fn from_map_value(&mut self, value: &MapValue) -> Result<(), oracle_nosql_rust_sdk::NoSQLError> {
                #fbody
                Ok(())
            }
        }

        impl NoSQLColumnToFieldValue for #name {
            fn to_field_value(&self) -> FieldValue {
                let m = self.to_map_value();
                if let Ok(mv) = m {
                    return FieldValue::Map(mv);
                }
                // TODO: How to expose this error??
                FieldValue::Null
            }
        }

        impl NoSQLColumnFromFieldValue for #name {
            fn from_field(fv: &FieldValue) -> Result<Self, oracle_nosql_rust_sdk::NoSQLError> {
                if let FieldValue::Map(v) = fv {
                    let mut s: #name = Default::default();
                    s.from_map_value(v)?;
                    return Ok(s);
                }
                Err(oracle_nosql_rust_sdk::NoSQLError::new(
                    oracle_nosql_rust_sdk::NoSQLErrorCode::IllegalArgument,
                    format!("NoSQL: Error converting field into {}: expected FieldValue::Map, actual: {:?}", #name_string, fv).as_str()))
            }
        }

    };

    //println!("expanded=\n{}\n", expanded);
    // Return the generated impl
    TokenStream::from(expanded)
}

fn get_path_segment(p: &TypePath, val: &str) -> String {
    for elem in &p.path.segments {
        let mut s = elem.ident.to_string();
        //println!("gps: elem={}", s);
        if let PathArguments::AngleBracketed(args) = &elem.arguments {
            for a in &args.args {
                if let GenericArgument::Type(tp) = a {
                    if let Type::Path(p1) = tp {
                        //println!("gps: recursing");
                        s = val.to_string() + &s;
                        return get_path_segment(&p1, &s);
                    }
                }
            }
            // TODO: error message?
            //println!("gps: error");
            return val.to_string();
        }
        // return after first element
        s = val.to_string() + &s;
        //println!("gps: returning {}", s);
        return s.to_string();
    }
    //println!("gps: returning empty={}", val);
    val.to_string()
}

/// (internal use only)
#[proc_macro_attribute]
pub fn add_planiter_fields(args: TokenStream, input: TokenStream) -> TokenStream {
    let mut item_struct = parse_macro_input!(input as syn::ItemStruct);
    let _ = parse_macro_input!(args as syn::parse::Nothing);

    if let syn::Fields::Named(ref mut fields) = item_struct.fields {
        //fields.named.push(syn::Field::parse_named.parse2(quote! { state: PlanIterState }).unwrap());
        fields.named.push(
            syn::Field::parse_named
                .parse2(quote! { result_reg: i32 })
                .unwrap(),
        );
        fields.named.push(
            syn::Field::parse_named
                .parse2(quote! { loc: Location })
                .unwrap(),
        );
    }

    return quote! {
        #item_struct
    }
    .into();
}