Skip to main content

dm_database_sqllog2db/config/
exporter.rs

1use crate::error::{ConfigError, Error, Result};
2use serde::Deserialize;
3
4#[derive(Debug, Deserialize, Clone)]
5pub struct ExporterConfig {
6    pub csv: Option<CsvExporterConfig>,
7    pub sqlite: Option<SqliteExporterConfig>,
8}
9
10impl ExporterConfig {
11    pub(super) fn has_any(&self) -> bool {
12        self.csv.is_some() || self.sqlite.is_some()
13    }
14
15    pub fn validate(&self) -> Result<()> {
16        if !self.has_any() {
17            return Err(Error::Config(ConfigError::NoExporters));
18        }
19        if let Some(csv) = &self.csv {
20            csv.validate()?;
21        }
22        if let Some(sqlite) = &self.sqlite {
23            sqlite.validate()?;
24        }
25        Ok(())
26    }
27}
28
29impl Default for ExporterConfig {
30    fn default() -> Self {
31        Self {
32            csv: Some(CsvExporterConfig::default()),
33            sqlite: None,
34        }
35    }
36}
37
38#[derive(Debug, Deserialize, Clone)]
39pub struct CsvExporterConfig {
40    pub file: String,
41    #[serde(default = "default_true")]
42    pub overwrite: bool,
43    #[serde(default)]
44    pub append: bool,
45    /// 关闭时跳过 `parse_performance_metrics()`,CSV 省略 `exectime/rowcount/exec_id` 三列。
46    #[serde(default = "default_true")]
47    pub include_performance_metrics: bool,
48}
49
50impl Default for CsvExporterConfig {
51    fn default() -> Self {
52        Self {
53            file: "outputs/sqllog.csv".to_string(),
54            overwrite: true,
55            append: false,
56            include_performance_metrics: true,
57        }
58    }
59}
60
61impl CsvExporterConfig {
62    pub fn validate(&self) -> Result<()> {
63        if self.file.trim().is_empty() {
64            return Err(Error::Config(ConfigError::InvalidValue {
65                field: "exporter.csv.file".to_string(),
66                value: self.file.clone(),
67                reason: "CSV output file path cannot be empty".to_string(),
68            }));
69        }
70        Ok(())
71    }
72}
73
74#[derive(Debug, Deserialize, Clone)]
75pub struct SqliteExporterConfig {
76    pub database_url: String,
77    #[serde(default = "default_table_name")]
78    pub table_name: String,
79    #[serde(default = "default_true")]
80    pub overwrite: bool,
81    #[serde(default)]
82    pub append: bool,
83    #[serde(default = "default_batch_size")]
84    pub batch_size: usize,
85}
86
87fn default_table_name() -> String {
88    "sqllog_records".to_string()
89}
90
91fn default_batch_size() -> usize {
92    10_000
93}
94
95impl Default for SqliteExporterConfig {
96    fn default() -> Self {
97        Self {
98            database_url: "export/sqllog2db.db".to_string(),
99            table_name: "sqllog_records".to_string(),
100            overwrite: true,
101            append: false,
102            batch_size: 10_000,
103        }
104    }
105}
106
107impl SqliteExporterConfig {
108    pub fn validate(&self) -> Result<()> {
109        if self.database_url.trim().is_empty() {
110            return Err(Error::Config(ConfigError::InvalidValue {
111                field: "exporter.sqlite.database_url".to_string(),
112                value: self.database_url.clone(),
113                reason: "SQLite database URL cannot be empty".to_string(),
114            }));
115        }
116        if self.table_name.trim().is_empty() {
117            return Err(Error::Config(ConfigError::InvalidValue {
118                field: "exporter.sqlite.table_name".to_string(),
119                value: self.table_name.clone(),
120                reason: "SQLite table name cannot be empty".to_string(),
121            }));
122        }
123        let is_valid_ident = {
124            let mut chars = self.table_name.chars();
125            chars
126                .next()
127                .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
128                && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
129        };
130        if !is_valid_ident {
131            return Err(Error::Config(ConfigError::InvalidValue {
132                field: "exporter.sqlite.table_name".to_string(),
133                value: self.table_name.clone(),
134                reason: "table name must match ^[a-zA-Z_][a-zA-Z0-9_]*$ (ASCII identifiers only)"
135                    .to_string(),
136            }));
137        }
138        if self.batch_size == 0 {
139            return Err(ConfigError::InvalidValue {
140                field: "exporter.sqlite.batch_size".to_string(),
141                value: "0".to_string(),
142                reason: "batch_size must be greater than 0".to_string(),
143            }
144            .into());
145        }
146        Ok(())
147    }
148}
149
150fn default_true() -> bool {
151    true
152}