risingwave_common_proc_macro/
session_config.rs1use bae::FromAttributes;
16use proc_macro_error::{OptionExt, ResultExt, abort};
17use proc_macro2::TokenStream;
18use quote::{format_ident, quote, quote_spanned};
19use syn::DeriveInput;
20
21#[derive(FromAttributes)]
22struct Parameter {
23 pub rename: Option<syn::LitStr>,
24 pub alias: Option<syn::Expr>,
25 pub default: syn::Expr,
26 pub flags: Option<syn::LitStr>,
27 pub check_hook: Option<syn::Expr>,
28 pub deprecated: Option<syn::LitStr>,
29}
30
31pub(crate) fn derive_config(input: DeriveInput) -> TokenStream {
32 let syn::Data::Struct(syn::DataStruct { fields, .. }) = input.data else {
33 abort!(input, "Only struct is supported");
34 };
35
36 let mut default_fields = vec![];
37 let mut struct_impl_set = vec![];
38 let mut struct_impl_get = vec![];
39 let mut struct_impl_reset = vec![];
40 let mut set_match_branches = vec![];
41 let mut get_match_branches = vec![];
42 let mut reset_match_branches = vec![];
43 let mut show_all_list = vec![];
44 let mut list_all_list = vec![];
45 let mut alias_to_entry_name_branches = vec![];
46 let mut entry_name_flags = vec![];
47 let mut session_init_fields = vec![];
50 let mut session_init_entries = vec![];
51
52 for field in fields {
53 let field_ident = field.ident.expect_or_abort("Field need to be named");
54 let ty = field.ty;
55
56 let mut doc_list = vec![];
57 for attr in &field.attrs {
58 if attr.path.is_ident("doc") {
59 let meta = attr.parse_meta().expect_or_abort("Failed to parse meta");
60 if let syn::Meta::NameValue(val) = meta
61 && let syn::Lit::Str(desc) = val.lit
62 {
63 doc_list.push(desc.value().trim().to_owned());
64 }
65 }
66 }
67
68 let description: TokenStream = format!("r#\"{}\"#", doc_list.join(" ")).parse().unwrap();
69
70 let attr =
71 Parameter::from_attributes(&field.attrs).expect_or_abort("Failed to parse attribute");
72 let Parameter {
73 rename,
74 alias,
75 default,
76 flags,
77 check_hook: check_hook_name,
78 deprecated,
79 } = attr;
80 let deprecated_attr = deprecated
81 .as_ref()
82 .map(|notice| quote! { #[deprecated(note = #notice)] })
83 .unwrap_or_else(|| quote! {});
84
85 let entry_name = if let Some(rename) = rename {
86 if !(rename.value().is_ascii() && rename.value().to_ascii_lowercase() == rename.value())
87 {
88 abort!(rename, "Expect `rename` to be an ascii lower case string");
89 }
90 quote! {#rename}
91 } else {
92 let ident = format_ident!("{}", field_ident.to_string().to_lowercase());
93 quote! {stringify!(#ident)}
94 };
95
96 if let Some(alias) = alias {
97 alias_to_entry_name_branches.push(quote! {
98 #alias => #entry_name.to_string(),
99 })
100 }
101
102 let flags = flags.map(|f| f.value()).unwrap_or_default();
103 let flags: Vec<_> = flags.split('|').map(|str| str.trim()).collect();
104
105 default_fields.push(quote_spanned! {
106 field_ident.span()=>
107 #field_ident: #default.into(),
108 });
109
110 let set_func_name = format_ident!("set_{}_str", field_ident);
111 let set_t_func_name = format_ident!("set_{}", field_ident);
112 let set_t_inner_func_name = format_ident!("set_{}_inner", field_ident);
113 let set_t_func_doc: TokenStream =
114 format!("r#\"Set parameter {} by a typed value.\"#", entry_name)
115 .parse()
116 .unwrap();
117 let set_func_doc: TokenStream = format!("r#\"Set parameter {} by a string.\"#", entry_name)
118 .parse()
119 .unwrap();
120
121 let gen_set_func_name = if flags.contains(&"SETTER") {
122 set_t_inner_func_name.clone()
123 } else {
124 set_t_func_name.clone()
125 };
126
127 let check_hook = if let Some(check_hook_name) = check_hook_name {
128 quote! {
129 #check_hook_name(&val).map_err(|e| {
130 SessionConfigError::InvalidValue {
131 entry: #entry_name,
132 value: val.to_string(),
133 source: anyhow::anyhow!(e),
134 }
135 })?;
136 }
137 } else {
138 quote! {}
139 };
140
141 let report_hook = if flags.contains(&"REPORT") {
142 quote! {
143 if self.#field_ident != val {
144 reporter.report_status(#entry_name, val.to_string());
145 }
146 }
147 } else {
148 quote! {}
149 };
150
151 let parse = if quote!(#ty).to_string() == "bool" {
153 quote!(risingwave_common::cast::str_to_bool)
154 } else {
155 quote!(<#ty as ::std::str::FromStr>::from_str)
156 };
157
158 struct_impl_set.push(quote! {
159 #deprecated_attr
160 #[doc = #set_func_doc]
161 pub fn #set_func_name(
162 &mut self,
163 val: &str,
164 reporter: &mut impl ConfigReporter
165 ) -> SessionConfigResult<String> {
166 let val_t = #parse(val).map_err(|e| {
167 SessionConfigError::InvalidValue {
168 entry: #entry_name,
169 value: val.to_string(),
170 source: anyhow::anyhow!(e),
171 }
172 })?;
173
174 self.#set_t_func_name(val_t, reporter).map(|val| val.to_string())
175 }
176
177 #deprecated_attr
178 #[doc = #set_t_func_doc]
179 pub fn #gen_set_func_name(
180 &mut self,
181 val: #ty,
182 reporter: &mut impl ConfigReporter
183 ) -> SessionConfigResult<#ty> {
184 #check_hook
185 #report_hook
186
187 self.#field_ident = val.clone();
188 Ok(val)
189 }
190
191 });
192
193 let reset_func_name = format_ident!("reset_{}", field_ident);
194 struct_impl_reset.push(quote! {
195
196 #deprecated_attr
197 #[allow(clippy::useless_conversion)]
198 pub fn #reset_func_name(&mut self, reporter: &mut impl ConfigReporter) -> String {
199 let val = #default;
200 #report_hook
201 self.#field_ident = val.into();
202 self.#field_ident.to_string()
203 }
204 });
205
206 let get_func_name = format_ident!("{}_str", field_ident);
207 let get_t_func_name = format_ident!("{}", field_ident);
208 let get_func_doc: TokenStream =
209 format!("r#\"Get a value string of parameter {} \"#", entry_name)
210 .parse()
211 .unwrap();
212 let get_t_func_doc: TokenStream =
213 format!("r#\"Get a typed value of parameter {} \"#", entry_name)
214 .parse()
215 .unwrap();
216
217 struct_impl_get.push(quote! {
218 #deprecated_attr
219 #[doc = #get_func_doc]
220 pub fn #get_func_name(&self) -> String {
221 self.#get_t_func_name().to_string()
222 }
223
224 #deprecated_attr
225 #[doc = #get_t_func_doc]
226 pub fn #get_t_func_name(&self) -> #ty {
227 self.#field_ident.clone()
228 }
229
230 });
231
232 get_match_branches.push(quote! {
233 #entry_name => Ok(self.#get_func_name()),
234 });
235
236 set_match_branches.push(quote! {
237 #entry_name => self.#set_func_name(&value, reporter),
238 });
239
240 reset_match_branches.push(quote! {
241 #entry_name => Ok(self.#reset_func_name(reporter)),
242 });
243
244 let var_info = quote! {
245 VariableInfo {
246 name: #entry_name.to_string(),
247 setting: self.#field_ident.to_string(),
248 description : #description.to_string(),
249 },
250 };
251 list_all_list.push(var_info.clone());
252
253 let no_show_all = flags.contains(&"NO_SHOW_ALL");
254 let no_show_all_flag: TokenStream = no_show_all.to_string().parse().unwrap();
255 if !no_show_all {
256 show_all_list.push(var_info);
257 }
258
259 let no_alter_sys_flag: TokenStream =
260 flags.contains(&"NO_ALTER_SYS").to_string().parse().unwrap();
261 let deprecated_notice = deprecated
262 .map(|notice| quote! { Some(#notice) })
263 .unwrap_or_else(|| quote! { None });
264
265 entry_name_flags.push(quote! {
266 (#entry_name, ParamFlags {
267 no_show_all: #no_show_all_flag,
268 no_alter_sys: #no_alter_sys_flag,
269 deprecated_notice: #deprecated_notice,
270 })
271 });
272
273 if flags.contains(&"SESSION_INIT") {
278 let doc_string = doc_list.join(" ");
279 session_init_fields.push(quote! {
280 #[doc = #doc_string]
281 #[serde(default, with = "crate::config::none_as_empty_string")]
282 pub #field_ident: Option<String>,
283 });
284 session_init_entries.push(quote! {
285 (#entry_name, &self.#field_ident),
286 });
287 }
288 }
289
290 let struct_ident = input.ident;
291 quote! {
292 #[derive(Clone, Debug, Default, Serialize, Deserialize, ConfigDoc, PartialEq)]
305 #[serde(deny_unknown_fields)]
306 pub struct SessionInitConfig {
307 #(#session_init_fields)*
308 }
309
310 impl SessionInitConfig {
311 pub fn entries(&self) -> Vec<(&'static str, &str)> {
314 [
315 #(#session_init_entries)*
316 ]
317 .into_iter()
318 .filter_map(|(name, value): (&'static str, &Option<String>)| {
319 value.as_deref().map(|value| (name, value))
320 })
321 .collect()
322 }
323 }
324
325 use std::collections::HashMap;
326 use std::sync::LazyLock;
327 static PARAM_NAME_FLAGS: LazyLock<HashMap<&'static str, ParamFlags>> = LazyLock::new(|| HashMap::from([#(#entry_name_flags, )*]));
328
329 struct ParamFlags {
330 no_show_all: bool,
331 no_alter_sys: bool,
332 deprecated_notice: Option<&'static str>,
333 }
334
335 impl Default for #struct_ident {
336 #[allow(clippy::useless_conversion, deprecated)]
337 fn default() -> Self {
338 Self {
339 #(#default_fields)*
340 }
341 }
342 }
343
344 #[allow(deprecated)]
345 impl #struct_ident {
346 fn new() -> Self {
347 Default::default()
348 }
349
350 pub fn alias_to_entry_name(key_name: &str) -> String {
351 let key_name = key_name.to_ascii_lowercase();
352 match key_name.as_str() {
353 #(#alias_to_entry_name_branches)*
354 _ => key_name,
355 }
356 }
357
358 #(#struct_impl_get)*
359
360 #(#struct_impl_set)*
361
362 #(#struct_impl_reset)*
363
364 pub fn set(&mut self, key_name: &str, value: String, reporter: &mut impl ConfigReporter) -> SessionConfigResult<String> {
366 let key_name = Self::alias_to_entry_name(key_name);
367 match key_name.as_ref() {
368 #(#set_match_branches)*
369 _ => Err(SessionConfigError::UnrecognizedEntry(key_name.to_string())),
370 }
371 }
372
373 pub fn get(&self, key_name: &str) -> SessionConfigResult<String> {
375 let key_name = Self::alias_to_entry_name(key_name);
376 match key_name.as_ref() {
377 #(#get_match_branches)*
378 _ => Err(SessionConfigError::UnrecognizedEntry(key_name.to_string())),
379 }
380 }
381
382 pub fn reset(&mut self, key_name: &str, reporter: &mut impl ConfigReporter) -> SessionConfigResult<String> {
384 let key_name = Self::alias_to_entry_name(key_name);
385 match key_name.as_ref() {
386 #(#reset_match_branches)*
387 _ => Err(SessionConfigError::UnrecognizedEntry(key_name.to_string())),
388 }
389 }
390
391 pub fn show_all(&self) -> Vec<VariableInfo> {
393 vec![
394 #(#show_all_list)*
395 ]
396 }
397
398 pub fn list_all(&self) -> Vec<VariableInfo> {
400 vec![
401 #(#list_all_list)*
402 ]
403 }
404
405 pub fn contains_param(key_name: &str) -> bool {
407 let key_name = Self::alias_to_entry_name(key_name);
408 PARAM_NAME_FLAGS.contains_key(key_name.as_str())
409 }
410
411 pub fn check_no_alter_sys(key_name: &str) -> SessionConfigResult<bool> {
413 let key_name = Self::alias_to_entry_name(key_name);
414 let flags = PARAM_NAME_FLAGS.get(key_name.as_str()).ok_or_else(|| SessionConfigError::UnrecognizedEntry(key_name.to_string()))?;
415 Ok(flags.no_alter_sys)
416 }
417
418 pub fn deprecated_notice(key_name: &str) -> SessionConfigResult<Option<&'static str>> {
420 let key_name = Self::alias_to_entry_name(key_name);
421 let flags = PARAM_NAME_FLAGS.get(key_name.as_str()).ok_or_else(|| SessionConfigError::UnrecognizedEntry(key_name.to_string()))?;
422 Ok(flags.deprecated_notice)
423 }
424 }
425 }
426}