Skip to main content

with_options/
lib.rs

1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use proc_macro::TokenStream;
16use quote::quote;
17use syn::{DeriveInput, parse_macro_input};
18
19/// Annotates that the struct represents the `WITH` properties for a connector.
20/// This implements a marker trait `WithOptions`.
21/// It's also used to generate the `with_option_*.yaml` files.
22///
23/// ## Notes about how to define property structs
24///
25/// ### Prefer strongly-typed fields
26///
27/// Avoid using `HashMap` with `#[serde(flatten)]` to include all unknown fields.
28/// Declare all fields explicitly instead.
29///
30/// The only exception now is CDC, which needs to pass a lot of options as-is to Debezium.
31///
32/// ### Common struct
33///
34/// When there are some fields can be grouped together, and/or can be shared by source and sink,
35/// or by multiple connectors, define a struct for them and use the common struct as a
36/// `#[serde(flatten)]` field.
37///
38/// Add `#[derive(WithOptions)]` to both the outer and the inner struct.
39///
40/// Avoid using nested `#[serde(flatten)]` field in the common struct,
41/// because this will lead to unexpected serde behaviors.
42/// Put all flatten fields in the top-level struct instead.
43///
44/// ## Field Annotations
45///
46/// ### Allow Alter On Fly fields (allowed in `alter table connector with`)
47///
48/// Use `#[with_option(allow_alter_on_fly)]` to mark fields that can be changed on the fly
49/// without requiring a restart or recreation of the connector:
50///
51/// ```ignore
52/// #[derive(WithOptions)]
53/// struct MyConnectorConfig {
54///     #[with_option(allow_alter_on_fly)]
55///     pub rate_limit: Option<u32>,
56///     pub endpoint: String,  // not allow_alter_on_fly
57/// }
58/// ```
59///
60/// ### Iceberg Engine fields
61///
62/// Use `#[with_option(iceberg_engine)]` to mark Iceberg sink fields that can be specified
63/// in `CREATE TABLE ... ENGINE = ICEBERG`. The frontend uses the generated field list to route
64/// these options from the table or source properties to the internal Iceberg sink.
65#[proc_macro_derive(WithOptions, attributes(with_option))]
66pub fn derive_helper_attr(input: TokenStream) -> TokenStream {
67    let input = parse_macro_input!(input as DeriveInput);
68
69    let fields = match input.data {
70        syn::Data::Struct(ref data) => match data.fields {
71            syn::Fields::Named(ref fields) => &fields.named,
72            _ => return quote! { compile_error!("WithOptions can only be derived for structs with named fields"); }.into(),
73        },
74        _ => return quote! { compile_error!("WithOptions can only be derived for structs"); }.into(),
75    };
76
77    let mut assert_impls = vec![];
78
79    for field in fields {
80        let field_name = field.ident.as_ref().unwrap();
81
82        assert_impls.push(quote!(
83            crate::with_options::WithOptions::assert_receiver_is_with_options(&self.#field_name);
84        ))
85    }
86
87    let struct_name = input.ident;
88    // This macro is only be expected to used in risingwave_connector. This trait is also defined there.
89    if input.generics.params.is_empty() {
90        quote! {
91            impl crate::with_options::WithOptions for #struct_name {
92                fn assert_receiver_is_with_options(&self) {
93                    #(#assert_impls)*
94                }
95            }
96        }
97        .into()
98    } else {
99        // Note: CDC properties have generics.
100        let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
101        quote! {
102            impl #impl_generics crate::with_options::WithOptions for #struct_name #ty_generics #where_clause {
103                fn assert_receiver_is_with_options(&self) {
104                    #(#assert_impls)*
105                }
106            }
107        }.into()
108    }
109}