Skip to main content

dm_database_sqllog2db/pipeline/
output.rs

1/// 导出字段名列表(顺序与 CSV/Parquet 列顺序一致,共 15 个字段)
2pub const FIELD_NAMES: &[&str] = &[
3    "ts",             // 0
4    "ep",             // 1
5    "sess_id",        // 2
6    "thrd_id",        // 3
7    "username",       // 4
8    "trx_id",         // 5
9    "statement",      // 6
10    "appname",        // 7
11    "client_ip",      // 8
12    "tag",            // 9
13    "sql",            // 10
14    "exec_time_ms",   // 11
15    "row_count",      // 12
16    "exec_id",        // 13
17    "normalized_sql", // 14
18];
19
20/// 字段投影掩码:u16 位图,bit i=1 表示导出第 i 个字段(共 15 个)
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct FieldMask(pub u16);
23
24impl FieldMask {
25    /// 全部 15 个字段都导出(默认值)
26    pub const ALL: Self = Self(0x7FFF);
27
28    /// 从字段名列表构建掩码,未知字段名返回错误消息
29    ///
30    /// # Errors
31    ///
32    /// 列表中包含未知字段名时返回含该字段名的错误消息。
33    pub fn from_names(names: &[String]) -> std::result::Result<Self, String> {
34        let mut mask = 0u16;
35        for name in names {
36            match FIELD_NAMES.iter().position(|&n| n == name.as_str()) {
37                Some(idx) => mask |= 1u16 << idx,
38                None => return Err(format!("unknown field: '{name}'")),
39            }
40        }
41        Ok(Self(mask))
42    }
43
44    /// 第 `idx` 个字段是否启用
45    #[inline]
46    #[must_use]
47    pub(crate) fn is_active(self, idx: usize) -> bool {
48        idx < 15 && (self.0 >> idx) & 1 == 1
49    }
50
51    /// `normalized_sql` 字段(索引 14)是否启用
52    #[inline]
53    #[must_use]
54    pub fn includes_normalized_sql(self) -> bool {
55        self.is_active(14)
56    }
57}
58
59impl Default for FieldMask {
60    fn default() -> Self {
61        Self::ALL
62    }
63}
64
65use serde::Deserialize;
66
67/// `[output]` 配置段:字段投影
68#[derive(Debug, Deserialize, Clone, Default)]
69pub struct OutputConfig {
70    /// 字段投影:仅导出指定字段,默认为全部 15 个字段
71    #[serde(default)]
72    pub fields: Option<Vec<String>>,
73}
74
75impl OutputConfig {
76    /// 计算字段投影掩码。字段名在 `validate()` 阶段已验证,无效名称静默退化为全量掩码。
77    #[must_use]
78    pub fn field_mask(&self) -> FieldMask {
79        match &self.fields {
80            None => FieldMask::ALL,
81            Some(names) if names.is_empty() => FieldMask::ALL,
82            Some(names) => FieldMask::from_names(names).unwrap_or(FieldMask::ALL),
83        }
84    }
85
86    /// 按用户配置顺序返回字段索引列表,供 exporter 写入时按序遍历。
87    #[must_use]
88    pub fn ordered_field_indices(&self) -> Vec<usize> {
89        match &self.fields {
90            None => (0..FIELD_NAMES.len()).collect(),
91            Some(names) if names.is_empty() => (0..FIELD_NAMES.len()).collect(),
92            Some(names) => names
93                .iter()
94                .filter_map(|name| FIELD_NAMES.iter().position(|&n| n == name.as_str()))
95                .collect(),
96        }
97    }
98}