faucet_sink_postgres/
config.rs1use faucet_core::DEFAULT_BATCH_SIZE;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
9#[serde(rename_all = "snake_case")]
10pub enum PostgresColumnMapping {
11 Jsonb { column: String },
14 AutoMap,
17}
18
19impl Default for PostgresColumnMapping {
20 fn default() -> Self {
21 Self::Jsonb {
22 column: "data".into(),
23 }
24 }
25}
26
27#[derive(Clone, Serialize, Deserialize, JsonSchema)]
29pub struct PostgresSinkConfig {
30 pub connection_url: String,
32 pub table_name: String,
34 #[serde(default)]
43 pub schema: Option<String>,
44 pub column_mapping: PostgresColumnMapping,
46 #[serde(default = "default_batch_size")]
67 pub batch_size: usize,
68 pub max_connections: u32,
70 #[serde(flatten)]
74 pub write: faucet_core::WriteSpec,
75}
76
77fn default_batch_size() -> usize {
78 DEFAULT_BATCH_SIZE
79}
80
81impl std::fmt::Debug for PostgresSinkConfig {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("PostgresSinkConfig")
84 .field("connection_url", &"***")
85 .field("table_name", &self.table_name)
86 .field("schema", &self.schema)
87 .field("column_mapping", &self.column_mapping)
88 .field("batch_size", &self.batch_size)
89 .field("max_connections", &self.max_connections)
90 .finish()
91 }
92}
93
94impl PostgresSinkConfig {
95 pub fn new(connection_url: impl Into<String>, table_name: impl Into<String>) -> Self {
97 Self {
98 connection_url: connection_url.into(),
99 table_name: table_name.into(),
100 schema: None,
101 column_mapping: PostgresColumnMapping::default(),
102 batch_size: DEFAULT_BATCH_SIZE,
103 max_connections: 5,
104 write: faucet_core::WriteSpec::default(),
105 }
106 }
107
108 pub fn with_schema(mut self, schema: impl Into<String>) -> Self {
111 self.schema = Some(schema.into());
112 self
113 }
114
115 pub fn column_mapping(mut self, mapping: PostgresColumnMapping) -> Self {
117 self.column_mapping = mapping;
118 self
119 }
120
121 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
127 self.batch_size = batch_size;
128 self
129 }
130
131 pub fn max_connections(mut self, n: u32) -> Self {
133 self.max_connections = n;
134 self
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn default_config() {
144 let config = PostgresSinkConfig::new("postgres://localhost/test", "events");
145 assert_eq!(config.table_name, "events");
146 assert_eq!(config.batch_size, DEFAULT_BATCH_SIZE);
147 assert!(matches!(
148 config.column_mapping,
149 PostgresColumnMapping::Jsonb { ref column } if column == "data"
150 ));
151 }
152
153 #[test]
154 fn builder_methods() {
155 let config = PostgresSinkConfig::new("postgres://localhost/test", "events")
156 .column_mapping(PostgresColumnMapping::AutoMap)
157 .with_batch_size(100);
158 assert_eq!(config.batch_size, 100);
159 assert!(matches!(
160 config.column_mapping,
161 PostgresColumnMapping::AutoMap
162 ));
163 }
164
165 #[test]
166 fn jsonb_custom_column() {
167 let config = PostgresSinkConfig::new("postgres://localhost/test", "events").column_mapping(
168 PostgresColumnMapping::Jsonb {
169 column: "payload".into(),
170 },
171 );
172 assert!(matches!(
173 config.column_mapping,
174 PostgresColumnMapping::Jsonb { ref column } if column == "payload"
175 ));
176 }
177
178 #[test]
179 fn with_batch_size_overrides_default() {
180 let config =
181 PostgresSinkConfig::new("postgres://localhost/test", "events").with_batch_size(250);
182 assert_eq!(config.batch_size, 250);
183 }
184
185 #[test]
186 fn batch_size_zero_is_accepted_as_no_batching_sentinel() {
187 let config =
188 PostgresSinkConfig::new("postgres://localhost/test", "events").with_batch_size(0);
189 assert_eq!(config.batch_size, 0);
190 assert!(faucet_core::validate_batch_size(config.batch_size).is_ok());
191 }
192
193 #[test]
194 fn batch_size_above_max_is_rejected_by_validate_batch_size() {
195 let config = PostgresSinkConfig::new("postgres://localhost/test", "events")
196 .with_batch_size(faucet_core::MAX_BATCH_SIZE + 1);
197 assert!(faucet_core::validate_batch_size(config.batch_size).is_err());
198 }
199
200 #[test]
201 fn batch_size_deserializes_from_json() {
202 let json = r#"{
203 "connection_url": "postgres://localhost/test",
204 "table_name": "events",
205 "column_mapping": {"jsonb": {"column": "data"}},
206 "batch_size": 250,
207 "max_connections": 5
208 }"#;
209 let config: PostgresSinkConfig = serde_json::from_str(json).unwrap();
210 assert_eq!(config.batch_size, 250);
211 }
212
213 #[test]
214 fn batch_size_defaults_when_absent_in_json() {
215 let json = r#"{
216 "connection_url": "postgres://localhost/test",
217 "table_name": "events",
218 "column_mapping": {"jsonb": {"column": "data"}},
219 "max_connections": 5
220 }"#;
221 let config: PostgresSinkConfig = serde_json::from_str(json).unwrap();
222 assert_eq!(config.batch_size, DEFAULT_BATCH_SIZE);
223 }
224
225 #[test]
226 fn config_builder_chaining() {
227 let config = PostgresSinkConfig::new("postgres://localhost/test", "events")
228 .with_batch_size(100)
229 .with_batch_size(250);
230 assert_eq!(config.batch_size, 250);
231 }
232}