Skip to main content

laminar_connectors/mongodb/
write_model.rs

1//! Write mode configuration for the `MongoDB` sink connector.
2//!
3//! Defines [`WriteMode`] which determines how incoming `RecordBatch` rows
4//! are translated into `MongoDB` write operations.
5
6use crate::error::ConnectorError;
7
8/// Write operation mode for the `MongoDB` sink.
9///
10/// Determines how incoming `RecordBatch` rows are translated into
11/// `MongoDB` write operations.
12#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
13#[serde(tag = "mode", rename_all = "snake_case")]
14pub enum WriteMode {
15    /// Append-only inserts using `insertOne` / `insertMany`.
16    #[default]
17    Insert,
18
19    /// Upsert by caller-supplied key fields. Uses `replaceOne` with
20    /// `upsert: true`, keyed by the specified fields.
21    Upsert {
22        /// Fields used to match existing documents for upsert.
23        key_fields: Vec<String>,
24    },
25
26    /// Routes operations based on the incoming event's `operationType`.
27    ///
28    /// Only valid for `LaminarEvent<MongoDbChangeEvent>` (CDC fan-out
29    /// replication). Maps operations as follows:
30    ///
31    /// - `insert` → idempotent `replaceOne(..., upsert: true)` by document key
32    /// - `update` → `updateOne` using `$set`/`$unset` from `updateDescription`
33    /// - `replace` → `replaceOne` with `upsert: true`
34    /// - `delete` → `deleteOne` using `documentKey._id`
35    /// - lifecycle and expanded events → rejected because a fixed destination cannot replay them
36    CdcReplay,
37}
38
39/// Validates that a write mode is compatible with time series collections.
40///
41/// Time series collections only accept `Insert`. Any other mode returns
42/// an error.
43///
44/// # Errors
45///
46/// Returns `ConnectorError::ConfigurationError` if the mode is not `Insert`.
47pub fn validate_timeseries_write_mode(mode: &WriteMode) -> Result<(), ConnectorError> {
48    if matches!(mode, WriteMode::Insert) {
49        Ok(())
50    } else {
51        Err(ConnectorError::ConfigurationError(format!(
52            "time series collections only support Insert write mode, got: {mode:?}"
53        )))
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn test_write_mode_default() {
63        assert!(matches!(WriteMode::default(), WriteMode::Insert));
64    }
65
66    #[test]
67    fn test_validate_timeseries_insert_ok() {
68        validate_timeseries_write_mode(&WriteMode::Insert).unwrap();
69    }
70
71    #[test]
72    fn test_validate_timeseries_upsert_fails() {
73        let mode = WriteMode::Upsert {
74            key_fields: vec!["id".to_string()],
75        };
76        let err = validate_timeseries_write_mode(&mode).unwrap_err();
77        assert!(err.to_string().contains("time series"));
78    }
79
80    #[test]
81    fn test_validate_timeseries_cdc_replay_fails() {
82        let err = validate_timeseries_write_mode(&WriteMode::CdcReplay).unwrap_err();
83        assert!(err.to_string().contains("time series"));
84    }
85}