stately_arrow/
database.rs

1// TODO: Docs
2#[cfg(feature = "clickhouse")]
3pub mod clickhouse;
4
5use serde::{Deserialize, Serialize};
6
7// The ClickHouse session context is the most strict, so it's used as the default when enabled
8/// Default query session context which provides support for any in-crate database backends.
9#[cfg(feature = "clickhouse")]
10pub type DefaultQuerySessionContext = clickhouse::QuerySessionContext;
11// In the case where `clickhouse` feature is not enabled, there is a simple implementation.
12/// Default query session context which provides support for any in-crate database backends.
13#[cfg(not(feature = "clickhouse"))]
14pub type DefaultQuerySessionContext = datafusion::execution::context::SessionContext;
15
16// TODO: Mark non_exhaustive and provide builder
17/// Configuration for database-backed connectors.
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, utoipa::ToSchema)]
19#[schema(as = DatabaseConfiguration)]
20pub struct Config {
21    pub options: ConnectionOptions,
22    pub driver:  Database,
23    #[serde(default)]
24    pub pool:    PoolOptions,
25}
26
27// TODO: Mark non_exhaustive and provide builder
28/// Common connection options shared by database connectors.
29#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, utoipa::ToSchema)]
30pub struct ConnectionOptions {
31    /// Endpoint, url, or path to the database
32    pub endpoint: String,
33    /// Username used to connect to the database
34    pub username: String,
35    /// Optional password for the database
36    #[serde(skip_serializing_if = "Option::is_none")]
37    #[schema(value_type = String)]
38    pub password: Option<Secret>,
39    /// TLS configuration if the database requires it
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub tls:      Option<TlsOptions>,
42    /// Whether the connector should validate connections before use
43    #[serde(default)]
44    #[schema(default = false)]
45    pub check:    bool,
46}
47
48/// Common configuration options shared across connector types.
49#[derive(
50    Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, utoipa::ToSchema,
51)]
52pub struct PoolOptions {
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub connect_timeout:     Option<u16>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub transaction_timeout: Option<u16>,
57    /// Configure the maximum number of connections to the database. Note, not all connectors
58    /// support pools.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub pool_size:           Option<u32>,
61}
62
63/// TLS options for databases that require secure connections.
64#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, utoipa::ToSchema)]
65pub struct TlsOptions {
66    #[serde(default)]
67    #[schema(default = false)]
68    pub enable: bool,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub domain: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub cafile: Option<String>,
73}
74
75/// Supported databases for the default backend lineup.
76///
77/// Default implementations will be provided and over time the list will grow. For that reason, this
78/// enum is marked as `non_exhaustive`.
79#[non_exhaustive]
80#[allow(missing_copy_implementations)] // Depending on feature flags, this triggers
81#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, utoipa::ToSchema)]
82#[cfg_attr(feature = "strum", derive(strum_macros::AsRefStr))]
83#[serde(rename_all = "snake_case")]
84pub enum Database {
85    #[cfg(feature = "clickhouse")]
86    #[serde(rename = "clickhouse", alias = "click_house")]
87    ClickHouse(
88        #[serde(default, skip_serializing_if = "Option::is_none")]
89        Option<clickhouse::ClickHouseConfig>,
90    ),
91}
92
93// TODO: Encrypt
94/// Newtype to protect secrets from being logged
95/// A wrapper type for sensitive string data like passwords.
96///
97/// This type provides protection against accidental exposure of sensitive data
98/// in logs, debug output, or error messages. The inner value is not displayed
99/// in `Debug` implementations.
100///
101/// # Example
102/// ```
103/// use stately_arrow::database::Secret;
104///
105/// let password = Secret::new("my_password");
106/// println!("{password:?}"); // Prints: Secret(*******)
107/// ```
108#[derive(Clone, Default, PartialEq, Eq, Hash, Deserialize, utoipa::ToSchema)]
109#[schema(value_type = String)]
110pub struct Secret(String);
111
112impl Secret {
113    pub fn new<P: AsRef<str>>(s: P) -> Self { Self(s.as_ref().to_string()) }
114
115    #[must_use]
116    pub fn get(&self) -> &str { &self.0 }
117}
118
119impl std::fmt::Debug for Secret {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        write!(f, "Secret(*****)")
122    }
123}
124
125impl<T: AsRef<str>> From<T> for Secret {
126    fn from(s: T) -> Self { Self(s.as_ref().to_string()) }
127}
128
129impl Serialize for Secret {
130    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131    where
132        S: serde::Serializer,
133    {
134        serializer.serialize_str(self.get())
135    }
136}