1
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{self, ext, parse_macro_input, DeriveInput, Expr, ExprLit, FieldsNamed, Ident, Lit, LitStr, Type};
5
6#[proc_macro_derive(DatabaseInteract, attributes(with_name, external))]
9pub fn database_interact_derive(input: TokenStream) -> TokenStream {
10 let input = parse_macro_input!(input as DeriveInput);
11 let struct_name = &input.ident;
12
13 let with_name_attr = input.attrs.iter().find_map(|attr| {
14 if attr.path().is_ident("with_name") {
15 let value: Expr = attr.parse_args().unwrap();
16 if let Expr::Lit(ExprLit { lit, .. }) = value {
17 if let Lit::Str(value) = lit {
18 return Some(value.value())
19 } else {
20 panic!("Invalid lit type")
21 }
22 } else {
23 panic!("Invalid type")
24 }
25 } else {
26 panic!("No table name provided")
27 }
28 }).expect("No with_name attribute");
29
30 let external = input.attrs.iter().find_map(|attr| {
31 if attr.path().is_ident("external") {
32 let value: Expr = attr.parse_args().unwrap();
33 if let Expr::Lit(ExprLit { lit, .. }) = value {
34 if let Lit::Str(value) = lit {
35 return Some(value.value().parse::<i64>().expect("Cannot parse external to i64"))
36 } else {
37 panic!("Invalid lit type")
38 }
39 } else {
40 panic!("Invalid type")
41 }
42 } else {
43 return None
44 }
45 });
46
47 let (is_external, external) = {
48 if let Some(external) = external {
49 (true, external)
50 } else {
51 (false, 0)
52 }
53 };
54
55 let idents: Vec<(Ident, usize, Ident)> = match input.data {
56 syn::Data::Struct(s) => match s.fields {
57 syn::Fields::Named(FieldsNamed { named, .. }) => {
58 named.iter().enumerate().map(|(idx, field)| {
59 let Type::Path(path) = &field.ty else {
60 panic!("unsupported field type")
61 };
62
63 (field.ident.clone().unwrap(), idx, path.path.segments[0].ident.clone())
64
65 }).collect()
66 }
67 _ => panic!("Unnamed structs are not supported.")
68 },
69
70 _ => panic!("Unsupported type.")
71 };
72 let field_literals: Vec<Lit> = idents
73 .iter()
74 .map(|ident| {
75 let field_str = LitStr::new(&ident.0.to_string(), ident.0.span());
76 Lit::Str(field_str)
77 })
78 .collect();
79
80 macro_rules! check_type {
81 ($t:expr, $($expected:literal),*) => {
82 matches!($t, $($expected)|*)
83 };
84 }
85
86 let construction_code = idents.iter().map(|(ident, _, field_type)| {
87 if check_type!(field_type.to_string().as_str(), "i64", "i128", "u64", "f64", "u32", "i32", "f32", "String", "Vec") {
88 quote! {
89 #ident: #ident.try_into().unwrap(),
90 }
91 } else {
92 quote! {
93 #ident,
94 }
95 }
96 });
97
98 let deser_code = idents.iter().map(|(ident, index, field_type)| {
99 let field_string = field_type.to_string();
100 let field_str = field_string.as_str();
101 if check_type!(field_type.to_string().as_str(), "i64", "i128", "u64", "f64", "u32", "i32", "f32", "String", "Vec") {
102 quote! {
103 let bytes = row.row.get(#index).unwrap();
104 let #ident = bincode::deserialize::<ZephyrVal>(&bytes.0).unwrap();
105
106 }
107 } else if check_type!(field_str, "ScVal", "Hash") {
108 quote! {
109 let bytes = row.row.get(#index).unwrap();
110 let #ident = ReadXdr::from_xdr(&bytes.0, Limits::none()).unwrap();
111
112 }
113 } else {
114 quote! {
115 let bytes = row.row.get(#index).unwrap();
116 let #ident = bincode::deserialize(&bytes.0).unwrap();
117
118 }
119 }
120 });
121
122 let serialize_type = idents.iter().map(|(ident, _, field_type)| {
123 if check_type!(field_type.to_string().as_str(), "i64", "i128", "u64", "f64", "u32", "i32", "f32", "String", "Vec") {
124 quote! {
125 bincode::serialize(&TryInto::<ZephyrVal>::try_into(self.#ident.clone()).unwrap()).unwrap().as_slice()
126 }
127 } else if check_type!(field_type.to_string().as_str(), "ScVal", "Hash") {
128 quote! {
129 self.#ident.clone().to_xdr(Limits::none()).unwrap().as_slice()
130 }
131 } else {
132 quote! {
133 bincode::serialize(&self.#ident).unwrap().as_slice()
134 }
135 }
136 });
137
138 let serialize_type_update = idents.iter().map(|(ident, _, field_type)| {
139 if check_type!(field_type.to_string().as_str(), "i64", "i128", "u64", "f64", "u32", "i32", "f32", "String", "Vec") {
140 quote! {
141 bincode::serialize(&TryInto::<ZephyrVal>::try_into(self.#ident.clone()).unwrap()).unwrap().as_slice()
142 }
143 } else if check_type!(field_type.to_string().as_str(), "ScVal", "Hash") {
144 quote! {
145 self.#ident.clone().to_xdr(Limits::none()).unwrap().as_slice()
146 }
147 } else {
148 quote! {
149 bincode::serialize(&self.#ident).unwrap().as_slice()
150 }
151 }
152 });
153
154 let expanded = quote! {
156 impl DatabaseInteract for #struct_name {
157 fn read_to_rows(env: &EnvClient, conditions: Option<&[Condition]>) -> Vec<Self> where Self: Sized {
158 let external = if #is_external {
159 Some(#external)
160 } else {
161 None
162 };
163
164 env.log().debug("calling the zephyr host db read", None);
165 let rows = env.db_read(&#with_name_attr, &[#(#field_literals),*], external, conditions);
166 if rows.is_err() {
167 env.log().debug(format!("dbread failed {:?}", rows.as_ref().err()), None);
168 }
169 let rows = rows.unwrap();
170 let mut result = Vec::new();
171
172 for row in rows.rows {
173 #(#deser_code)*
174 result.push(Self {
175 #(#construction_code)*
176 });
177 }
178
179
180 result
181 }
182
183 fn put(&self, env: &EnvClient) {
184 env.db_write(&#with_name_attr, &[#(#field_literals),*], &[#(#serialize_type),*]).unwrap();
185 }
186
187 fn update(&self, env: &EnvClient, conditions: &[Condition]) {
188 env.db_update(&#with_name_attr, &[#(#field_literals),*], &[#(#serialize_type_update),*], conditions).unwrap();
189 }
190 }
191 };
192
193
194 TokenStream::from(expanded)
195}
196