laminar_connectors/mongodb/sink/
lifecycle.rs1use super::{
4 async_trait, info, Arc, ConnectorConfig, ConnectorError, ConnectorState, ConnectorTaskTracker,
5 Duration, MongoDbSink, RecordBatch, SchemaRef, SinkConnector, SinkConsistency, SinkContract,
6 SinkInputMode, SinkTopology, WriteMode, WriteResult, MAX_BUFFERED_RETAINED_BYTES,
7};
8
9#[async_trait]
10impl SinkConnector for MongoDbSink {
11 fn terminal_task_tracker(&self) -> Option<ConnectorTaskTracker> {
12 Some(self.task_tracker.clone())
13 }
14
15 fn contract(&self, config: &ConnectorConfig) -> Result<SinkContract, ConnectorError> {
16 let (cfg, schema, _) = if config.properties().is_empty() {
17 (
18 self.config.clone(),
19 Arc::clone(&self.schema),
20 self.write_timeout,
21 )
22 } else {
23 Self::decode_connector_config(config)?
24 };
25 cfg.validate()?;
26 Self::validate_schema(&schema, &cfg)?;
27 let (topology, input_mode) = match cfg.write_mode {
30 WriteMode::Insert => (SinkTopology::MultiWriter, SinkInputMode::AppendOnly),
31 WriteMode::CdcReplay => (SinkTopology::Singleton, SinkInputMode::AppendOnly),
32 WriteMode::Upsert { .. } => (SinkTopology::Singleton, SinkInputMode::FullChangelog),
33 };
34 Ok(SinkContract::new(
35 SinkConsistency::DurableAtLeastOnce,
36 topology,
37 input_mode,
38 ))
39 }
40
41 async fn resolve_schema(
42 &mut self,
43 config: &ConnectorConfig,
44 input: SchemaRef,
45 ) -> Result<crate::schema::resolution::SchemaBinding, ConnectorError> {
46 let parsed = super::super::config::MongoDbSinkConfig::from_config(config)?;
47 super::schema_resolution::resolve(config, &parsed, input).await
48 }
49
50 async fn open(&mut self, config: &ConnectorConfig) -> Result<(), ConnectorError> {
51 if self.state != ConnectorState::Created {
52 return Err(ConnectorError::InvalidState {
53 expected: ConnectorState::Created.to_string(),
54 actual: self.state.to_string(),
55 });
56 }
57 if config.properties().is_empty() {
58 self.config.validate()?;
59 Self::validate_schema(&self.schema, &self.config)?;
60 } else {
61 self.apply_connector_config(config)?;
62 }
63 let mut current =
64 super::schema_resolution::resolve(config, &self.config, self.schema.clone()).await?;
65 if config
66 .schema_binding()
67 .is_some_and(|binding| binding.value != current.value)
68 {
69 return Err(ConnectorError::SchemaMismatch("MongoDB collection identity or validator changed before activation; migrate the binding".into()));
70 }
71 if config.schema_binding().is_none() {
72 self.prepare_target(config, &mut current).await?;
73 }
74 if current.value.is_none() {
75 return Err(ConnectorError::SchemaMismatch(
76 "MongoDB activation requires a prepared collection UUID".into(),
77 ));
78 }
79 self.binding = Some(current);
80 self.connect().await?;
81
82 self.state = ConnectorState::Running;
83 info!(
84 database = %self.config.database,
85 collection = %self.config.collection,
86 write_mode = ?self.config.write_mode,
87 "MongoDB sink opened"
88 );
89
90 Ok(())
91 }
92
93 async fn prepare_schema(
94 &mut self,
95 config: &ConnectorConfig,
96 binding: &mut crate::schema::resolution::SchemaBinding,
97 ) -> Result<(), ConnectorError> {
98 self.prepare_target(config, binding).await
99 }
100
101 async fn write_batch(&mut self, batch: &RecordBatch) -> Result<WriteResult, ConnectorError> {
102 self.write_batch_with_retained_limit(batch, MAX_BUFFERED_RETAINED_BYTES)
103 .await
104 }
105
106 fn schema(&self) -> SchemaRef {
107 Arc::clone(&self.schema)
108 }
109
110 fn suggested_write_timeout(&self) -> Duration {
111 self.write_timeout
112 }
113
114 fn flush_interval(&self) -> Duration {
115 self.config.flush_interval()
116 }
117
118 async fn flush(&mut self) -> Result<(), ConnectorError> {
119 if self.state != ConnectorState::Running {
120 return Err(ConnectorError::InvalidState {
121 expected: ConnectorState::Running.to_string(),
122 actual: self.state.to_string(),
123 });
124 }
125 self.flush_inner().await.map(|_| ())
126 }
127
128 async fn close(&mut self) -> Result<(), ConnectorError> {
129 if self.state == ConnectorState::Closed {
130 return Ok(());
131 }
132
133 let flush_result = if self.state == ConnectorState::Running && !self.buffer.is_empty() {
134 self.flush_inner().await.map(|_| ())
135 } else {
136 drop(self.take_buffer());
137 Ok(())
138 };
139 self.collection = None;
140 self.client = None;
141 self.state = ConnectorState::Closed;
142 info!("MongoDB sink closed");
143 flush_result
144 }
145}