risingwave_license/
feature.rs

1// Copyright 2025 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 thiserror::Error;
16
17use super::{LicenseError, LicenseManager, Tier, report_telemetry};
18
19/// Define all features that are available based on the tier of the license.
20///
21/// # Define a new feature
22///
23/// To add a new feature, add a new entry below following the same pattern as the existing ones.
24///
25/// Check the definition of [`Tier`] for all available tiers. Note that normally there's no need to
26/// add a feature with the minimum tier of `Free`, as you can directly write the code without
27/// gating it with a feature check.
28///
29/// # Check the availability of a feature
30///
31/// To check the availability of a feature during runtime, call the method
32/// [`check_available`](Feature::check_available) on the feature. If the feature is not available,
33/// an error of type [`FeatureNotAvailable`] will be returned and you should handle it properly,
34/// generally by returning an error to the user.
35///
36/// # Feature availability in tests
37///
38/// In tests with `debug_assertions` enabled, a license key of the paid (maximum) tier is set by
39/// default. As a result, all features are available in tests. To test the behavior when a feature
40/// is not available, you can manually set a license key with a lower tier. Check the e2e test cases
41/// under `error_ui` for examples.
42macro_rules! for_all_features {
43    ($macro:ident) => {
44        $macro! {
45            // name                      min tier    doc
46            { TestPaid,                  Paid,       "A dummy feature that's only available on paid tier for testing purposes." },
47            { TimeTravel,                Paid,       "Query historical data within the retention period."},
48            { GlueSchemaRegistry,        Paid,       "Use Schema Registry from AWS Glue rather than Confluent." },
49            { SnowflakeSink,             Paid,       "Delivering data to SnowFlake." },
50            { DynamoDbSink,              Paid,       "Delivering data to DynamoDb." },
51            { OpenSearchSink,            Paid,       "Delivering data to OpenSearch." },
52            { BigQuerySink,              Paid,       "Delivering data to BigQuery." },
53            { ClickHouseSharedEngine,    Paid,       "Delivering data to Shared tree on clickhouse cloud"},
54            { SecretManagement,          Paid,       "Secret management." },
55            { CdcTableSchemaMap,         Paid,       "Automatically map upstream schema to CDC Table."},
56            { SqlServerSink,             Paid,       "Sink data from RisingWave to SQL Server." },
57            { SqlServerCdcSource,        Paid,       "CDC source connector for Sql Server." },
58            { CdcAutoSchemaChange,       Paid,       "Auto replicate upstream DDL to CDC Table." },
59            { IcebergSinkWithGlue,       Paid,       "Delivering data to Iceberg with Glue catalog." },
60            { ElasticDiskCache,          Paid,       "Disk cache and refilling to boost performance and reduce object store access cost." },
61            { ResourceGroup,             Paid,       "Resource group to isolate workload and failure." },
62            { DatabaseFailureIsolation,  Paid,       "Failure isolation between databases" },
63        }
64    };
65}
66
67macro_rules! def_feature {
68    ($({ $name:ident, $min_tier:ident, $doc:literal },)*) => {
69        /// A set of features that are available based on the tier of the license.
70        ///
71        /// To define a new feature, add a new entry in the macro [`for_all_features`].
72        #[derive(Clone, Copy, Debug)]
73        pub enum Feature {
74            $(
75                #[doc = concat!($doc, "\n\nAvailable for tier `", stringify!($min_tier), "` and above.")]
76                $name,
77            )*
78        }
79
80        impl Feature {
81            /// Minimum tier required to use this feature.
82            fn min_tier(self) -> Tier {
83                match self {
84                    $(
85                        Self::$name => Tier::$min_tier,
86                    )*
87                }
88            }
89
90            fn get_feature_name(&self) -> &'static str {
91                match &self {
92                    $(
93                        Self::$name => stringify!($name),
94                    )*
95                }
96            }
97        }
98    };
99}
100
101for_all_features!(def_feature);
102
103/// The error type for feature not available due to license.
104#[derive(Debug, Error)]
105pub enum FeatureNotAvailable {
106    #[error(
107    "feature {:?} is only available for tier {:?} and above, while the current tier is {:?}\n\n\
108        Hint: You may want to set a license key with `ALTER SYSTEM SET license_key = '...';` command.",
109    feature, feature.min_tier(), current_tier,
110    )]
111    InsufficientTier {
112        feature: Feature,
113        current_tier: Tier,
114    },
115
116    #[error("feature {feature:?} is not available due to license error")]
117    LicenseError {
118        feature: Feature,
119        source: LicenseError,
120    },
121}
122
123impl Feature {
124    /// Check whether the feature is available based on the given license manager.
125    pub(crate) fn check_available_with(
126        self,
127        manager: &LicenseManager,
128    ) -> Result<(), FeatureNotAvailable> {
129        let check_res = match manager.license() {
130            Ok(license) => {
131                if license.tier >= self.min_tier() {
132                    Ok(())
133                } else {
134                    Err(FeatureNotAvailable::InsufficientTier {
135                        feature: self,
136                        current_tier: license.tier,
137                    })
138                }
139            }
140            Err(error) => Err(FeatureNotAvailable::LicenseError {
141                feature: self,
142                source: error,
143            }),
144        };
145
146        report_telemetry(&self, self.get_feature_name(), check_res.is_ok());
147
148        check_res
149    }
150
151    /// Check whether the feature is available based on the current license.
152    pub fn check_available(self) -> Result<(), FeatureNotAvailable> {
153        self.check_available_with(LicenseManager::get())
154    }
155}