1pub mod prelude {
16 pub use crate::source::datagen::DatagenSplitEnumerator;
18 pub use crate::source::filesystem::LegacyS3SplitEnumerator;
19 pub use crate::source::filesystem::opendal_source::OpendalEnumerator;
20 pub use crate::source::google_pubsub::PubsubSplitEnumerator as GooglePubsubSplitEnumerator;
21 pub use crate::source::iceberg::IcebergSplitEnumerator;
22 pub use crate::source::kafka::KafkaSplitEnumerator;
23 pub use crate::source::kinesis::KinesisSplitEnumerator;
24 pub use crate::source::mqtt::MqttSplitEnumerator;
25 pub use crate::source::nats::NatsSplitEnumerator;
26 pub use crate::source::nexmark::NexmarkSplitEnumerator;
27 pub use crate::source::pulsar::PulsarSplitEnumerator;
28 pub use crate::source::test_source::TestSourceSplitEnumerator as TestSplitEnumerator;
29 pub type AzblobSplitEnumerator =
30 OpendalEnumerator<crate::source::filesystem::opendal_source::OpendalAzblob>;
31 pub type GcsSplitEnumerator =
32 OpendalEnumerator<crate::source::filesystem::opendal_source::OpendalGcs>;
33 pub type OpendalS3SplitEnumerator =
34 OpendalEnumerator<crate::source::filesystem::opendal_source::OpendalS3>;
35 pub type PosixFsSplitEnumerator =
36 OpendalEnumerator<crate::source::filesystem::opendal_source::OpendalPosixFs>;
37 pub use crate::source::cdc::enumerator::DebeziumSplitEnumerator;
38 pub use crate::source::filesystem::opendal_source::BatchPosixFsEnumerator as BatchPosixFsSplitEnumerator;
39 pub type CitusCdcSplitEnumerator = DebeziumSplitEnumerator<crate::source::cdc::Citus>;
40 pub type MongodbCdcSplitEnumerator = DebeziumSplitEnumerator<crate::source::cdc::Mongodb>;
41 pub type PostgresCdcSplitEnumerator = DebeziumSplitEnumerator<crate::source::cdc::Postgres>;
42 pub type MysqlCdcSplitEnumerator = DebeziumSplitEnumerator<crate::source::cdc::Mysql>;
43 pub type SqlServerCdcSplitEnumerator = DebeziumSplitEnumerator<crate::source::cdc::SqlServer>;
44}
45
46pub mod base;
47pub mod batch;
48pub mod cdc;
49pub mod data_gen_util;
50pub mod datagen;
51pub mod filesystem;
52pub mod google_pubsub;
53pub mod kafka;
54pub mod kinesis;
55pub mod monitor;
56pub mod mqtt;
57pub mod nats;
58pub mod nexmark;
59pub mod pulsar;
60pub mod utils;
61
62mod util;
63use std::collections::HashMap;
64use std::future::IntoFuture;
65use std::time::Duration;
66
67pub use base::{UPSTREAM_SOURCE_KEY, WEBHOOK_CONNECTOR, *};
68pub use batch::BatchSourceSplitImpl;
69pub(crate) use common::*;
70use google_cloud_pubsub::subscription::Subscription;
71pub use google_pubsub::GOOGLE_PUBSUB_CONNECTOR;
72pub use kafka::KAFKA_CONNECTOR;
73pub use kinesis::KINESIS_CONNECTOR;
74use monitor::{ConnectorAckFailureType, GLOBAL_SOURCE_METRICS};
75pub use mqtt::MQTT_CONNECTOR;
76pub use nats::NATS_CONNECTOR;
77use utils::feature_gated_source_mod;
78
79pub use self::adbc_snowflake::ADBC_SNOWFLAKE_CONNECTOR;
80mod common;
81pub mod iceberg;
82mod manager;
83pub mod reader;
84pub mod test_source;
85feature_gated_source_mod!(adbc_snowflake, "adbc_snowflake");
86
87use async_nats::jetstream::consumer::AckPolicy as JetStreamAckPolicy;
88use async_nats::jetstream::context::Context as JetStreamContext;
89pub use manager::{SourceColumnDesc, SourceColumnType};
90use risingwave_common::array::{Array, ArrayRef};
91use risingwave_common::row::OwnedRow;
92use risingwave_pb::id::{ActorId, SourceId};
93use thiserror_ext::AsReport;
94pub use util::fill_adaptive_split;
95
96pub use crate::source::filesystem::LEGACY_S3_CONNECTOR;
97pub use crate::source::filesystem::opendal_source::{
98 AZBLOB_CONNECTOR, BATCH_POSIX_FS_CONNECTOR, GCS_CONNECTOR, OPENDAL_S3_CONNECTOR,
99 POSIX_FS_CONNECTOR,
100};
101pub use crate::source::nexmark::NEXMARK_CONNECTOR;
102pub use crate::source::pulsar::PULSAR_CONNECTOR;
103use crate::source::pulsar::source::reader::PULSAR_ACK_CHANNEL;
104
105pub fn should_copy_to_format_encode_options(key: &str, connector: &str) -> bool {
106 const PREFIXES: &[&str] = &[
107 "schema.registry",
108 "schema.location",
109 "message",
110 "key.message",
111 "without_header",
112 "delimiter",
113 "region",
115 "endpoint_url",
116 "access_key",
117 "secret_key",
118 "session_token",
119 "arn",
120 "external_id",
121 "profile",
122 ];
123 PREFIXES.iter().any(|prefix| key.starts_with(prefix))
124 || (key == "endpoint" && !connector.eq_ignore_ascii_case(KINESIS_CONNECTOR))
125}
126
127pub enum WaitCheckpointTask {
129 CommitCdcOffset(Option<(SplitId, String)>),
130 AckPubsubMessage(Subscription, Vec<ArrayRef>),
131 AckNatsJetStream(JetStreamContext, Vec<ArrayRef>, JetStreamAckPolicy),
132 AckPulsarMessage(Vec<(String, ArrayRef)>),
133}
134
135impl WaitCheckpointTask {
136 pub fn reset_for_next_epoch(&self) -> Self {
140 match self {
141 WaitCheckpointTask::CommitCdcOffset(_) => WaitCheckpointTask::CommitCdcOffset(None),
142 WaitCheckpointTask::AckPubsubMessage(subscription, _) => {
143 WaitCheckpointTask::AckPubsubMessage(subscription.clone(), vec![])
144 }
145 WaitCheckpointTask::AckNatsJetStream(context, _, ack_policy) => {
146 WaitCheckpointTask::AckNatsJetStream(context.clone(), vec![], *ack_policy)
147 }
148 WaitCheckpointTask::AckPulsarMessage(_) => WaitCheckpointTask::AckPulsarMessage(vec![]),
149 }
150 }
151
152 pub async fn run(self, source_id: SourceId, source_name: &str) {
153 self.run_with_on_commit_success(source_id, source_name, |_source_id, _offset| {
154 })
156 .await;
157 }
158
159 pub async fn run_with_on_commit_success<F>(
160 self,
161 source_id: SourceId,
162 source_name: &str,
163 mut on_commit_success: F,
164 ) where
165 F: FnMut(u64, &str),
166 {
167 use std::str::FromStr;
168 let source_id_label = source_id.to_string();
169 match self {
170 WaitCheckpointTask::CommitCdcOffset(updated_offset) => {
171 if let Some((split_id, offset)) = updated_offset {
172 let committed_source_id: u64 = u64::from_str(split_id.as_ref()).unwrap();
173 match cdc::jni_source::commit_cdc_offset(committed_source_id, offset.clone()) {
175 Ok(()) => {
176 on_commit_success(committed_source_id, &offset);
178 }
179 Err(e) => {
180 tracing::error!(
181 source_id = committed_source_id,
182 source_name,
183 error = %e.as_report(),
184 "source#{committed_source_id}: failed to commit cdc offset: {offset}.",
185 )
186 }
187 }
188 }
189 }
190 WaitCheckpointTask::AckPulsarMessage(ack_array) => {
191 let mut latest_ack_by_channel = HashMap::new();
192 for (ack_channel_id, to_cumulative_ack) in ack_array {
193 let encode_message_id_data = to_cumulative_ack
194 .as_bytea()
195 .iter()
196 .flatten()
197 .last()
198 .map(|message_id| message_id.to_owned());
199
200 if let Some(encode_message_id_data) = encode_message_id_data {
201 latest_ack_by_channel.insert(ack_channel_id, Some(encode_message_id_data));
202 } else {
203 latest_ack_by_channel.entry(ack_channel_id).or_insert(None);
204 }
205 }
206
207 for (ack_channel_id, encode_message_id_data) in latest_ack_by_channel {
208 let Some(encode_message_id_data) = encode_message_id_data else {
209 GLOBAL_SOURCE_METRICS.inc_connector_ack_failure_count(
210 source_name,
211 "pulsar",
212 ConnectorAckFailureType::EmptyMessageId,
213 );
214 tracing::warn!(
215 source_id = source_id_label,
216 source_name,
217 ack_channel_id,
218 "skip Pulsar ack because the checkpoint ack batches have no message id",
219 );
220 continue;
221 };
222
223 let Some(ack_tx) = PULSAR_ACK_CHANNEL.lock().get(&ack_channel_id).cloned()
224 else {
225 GLOBAL_SOURCE_METRICS.inc_connector_ack_failure_count(
226 source_name,
227 "pulsar",
228 ConnectorAckFailureType::ChannelMissing,
229 );
230 tracing::warn!(
231 source_id = source_id_label,
232 source_name,
233 ack_channel_id,
234 "skip Pulsar ack because the ack channel is missing",
235 );
236 continue;
237 };
238
239 if let Err(e) = ack_tx.send(encode_message_id_data) {
240 GLOBAL_SOURCE_METRICS.inc_connector_ack_failure_count(
241 source_name,
242 "pulsar",
243 ConnectorAckFailureType::ChannelSendError,
244 );
245 tracing::warn!(
246 source_id = source_id_label,
247 source_name,
248 ack_channel_id,
249 error = %e.as_report(),
250 "failed to send Pulsar ack message id to the reader ack channel",
251 );
252 }
253 }
254 }
255 WaitCheckpointTask::AckPubsubMessage(subscription, ack_id_arrs) => {
256 const ACK_RPC_TIMEOUT: Duration = Duration::from_secs(30);
257 async fn ack(
258 subscription: &Subscription,
259 ack_ids: Vec<String>,
260 source_id_label: &str,
261 source_name: &str,
262 ) {
263 if ack_ids.is_empty() {
264 return;
265 }
266 tracing::trace!("acking pubsub messages {:?}", ack_ids);
267 match tokio::time::timeout(ACK_RPC_TIMEOUT, subscription.ack(ack_ids)).await {
268 Ok(Ok(())) => {
269 GLOBAL_SOURCE_METRICS
270 .inc_connector_ack_success_count(source_name, "pubsub");
271 }
272 Ok(Err(e)) => {
273 GLOBAL_SOURCE_METRICS.inc_connector_ack_failure_count(
274 source_name,
275 "pubsub",
276 ConnectorAckFailureType::Error,
277 );
278 tracing::error!(
279 source_id = source_id_label,
280 source_name,
281 error = %e.as_report(),
282 "failed to ack pubsub messages",
283 )
284 }
285 Err(_) => {
286 GLOBAL_SOURCE_METRICS.inc_connector_ack_failure_count(
287 source_name,
288 "pubsub",
289 ConnectorAckFailureType::Timeout,
290 );
291 tracing::error!(
292 source_id = source_id_label,
293 source_name,
294 "pubsub ack timed out after {ACK_RPC_TIMEOUT:?}",
295 )
296 }
297 }
298 }
299 const MAX_ACK_BATCH_SIZE: usize = 1000;
300 let mut ack_ids: Vec<String> = vec![];
301 for arr in ack_id_arrs {
302 for ack_id in arr.as_utf8().iter().flatten() {
303 ack_ids.push(ack_id.to_owned());
304 if ack_ids.len() >= MAX_ACK_BATCH_SIZE {
305 ack(
306 &subscription,
307 std::mem::take(&mut ack_ids),
308 &source_id_label,
309 source_name,
310 )
311 .await;
312 }
313 }
314 }
315 ack(&subscription, ack_ids, &source_id_label, source_name).await;
316 }
317 WaitCheckpointTask::AckNatsJetStream(
318 ref context,
319 reply_subjects_arrs,
320 ref ack_policy,
321 ) => {
322 const ACK_RPC_TIMEOUT: Duration = Duration::from_secs(30);
323 async fn ack(
324 context: &JetStreamContext,
325 reply_subject: String,
326 source_id_label: &str,
327 source_name: &str,
328 ) {
329 let fut = async {
330 let ack_future = context
331 .publish(reply_subject.clone(), "+ACK".into())
332 .await
333 .map_err(|e| e.to_report_string())?;
334 ack_future
335 .into_future()
336 .await
337 .map_err(|e| e.to_report_string())?;
338 Ok::<(), String>(())
339 };
340 match tokio::time::timeout(ACK_RPC_TIMEOUT, fut).await {
341 Ok(Ok(())) => {
342 GLOBAL_SOURCE_METRICS
343 .inc_connector_ack_success_count(source_name, "nats_jetstream");
344 }
345 Ok(Err(e)) => {
346 GLOBAL_SOURCE_METRICS.inc_connector_ack_failure_count(
347 source_name,
348 "nats_jetstream",
349 ConnectorAckFailureType::Error,
350 );
351 tracing::error!(
352 source_id = source_id_label,
353 source_name,
354 error = %e,
355 subject = ?reply_subject,
356 "failed to ack NATS JetStream message",
357 );
358 }
359 Err(_) => {
360 GLOBAL_SOURCE_METRICS.inc_connector_ack_failure_count(
361 source_name,
362 "nats_jetstream",
363 ConnectorAckFailureType::Timeout,
364 );
365 tracing::error!(
366 source_id = source_id_label,
367 source_name,
368 subject = ?reply_subject,
369 "NATS JetStream ack timed out after {ACK_RPC_TIMEOUT:?}",
370 );
371 }
372 }
373 }
374
375 let reply_subjects = reply_subjects_arrs
376 .iter()
377 .flat_map(|arr| {
378 arr.as_utf8()
379 .iter()
380 .flatten()
381 .map(|s| s.to_owned())
382 .collect::<Vec<String>>()
383 })
384 .collect::<Vec<String>>();
385
386 match ack_policy {
387 JetStreamAckPolicy::None | JetStreamAckPolicy::FlowControl => (),
388 JetStreamAckPolicy::Explicit => {
389 for reply_subject in reply_subjects {
390 if reply_subject.is_empty() {
391 continue;
392 }
393 ack(context, reply_subject, &source_id_label, source_name).await;
394 }
395 }
396 JetStreamAckPolicy::All => {
397 if let Some(reply_subject) = reply_subjects.last() {
398 ack(
399 context,
400 reply_subject.clone(),
401 &source_id_label,
402 source_name,
403 )
404 .await;
405 }
406 }
407 }
408 }
409 }
410 }
411}
412
413#[derive(Clone, Debug, PartialEq)]
414pub struct CdcTableSnapshotSplitCommon<T: Clone> {
415 pub split_id: i64,
416 pub left_bound_inclusive: T,
417 pub right_bound_exclusive: T,
418}
419
420pub type CdcTableSnapshotSplit = CdcTableSnapshotSplitCommon<OwnedRow>;
421pub type CdcTableSnapshotSplitRaw = CdcTableSnapshotSplitCommon<Vec<u8>>;
422
423#[inline]
428pub fn build_pulsar_ack_channel_id(
429 source_id: SourceId,
430 split_id: &SplitId,
431 actor_id: ActorId,
432) -> String {
433 format!("{}-{}-{}", source_id, split_id, actor_id)
434}
435
436#[cfg(test)]
437mod tests {
438 use risingwave_common::array::{Array, BytesArray};
439 use tokio::sync::mpsc::error::TryRecvError;
440
441 use super::*;
442
443 #[test]
444 fn test_pulsar_ack_channel_id_is_actor_scoped() {
445 let source_id = SourceId::new(7);
446 let split_id: SplitId = "persistent://public/default/topic".into();
447
448 let first = build_pulsar_ack_channel_id(source_id, &split_id, ActorId::new(11));
449 let second = build_pulsar_ack_channel_id(source_id, &split_id, ActorId::new(12));
450
451 assert_ne!(first, second);
452 }
453
454 fn message_ids<const N: usize>(values: [Option<&[u8]>; N]) -> ArrayRef {
455 BytesArray::from_iter(values).into_ref()
456 }
457
458 #[tokio::test]
459 async fn test_ack_pulsar_message_for_each_split() {
460 let split_0_channel = "test-pulsar-ack-multiple-splits-0".to_owned();
461 let split_1_channel = "test-pulsar-ack-multiple-splits-1".to_owned();
462 let empty_split_channel = "test-pulsar-ack-multiple-splits-empty".to_owned();
463 let (split_0_tx, mut split_0_rx) = tokio::sync::mpsc::unbounded_channel();
464 let (split_1_tx, mut split_1_rx) = tokio::sync::mpsc::unbounded_channel();
465 let (empty_split_tx, mut empty_split_rx) = tokio::sync::mpsc::unbounded_channel();
466
467 PULSAR_ACK_CHANNEL
468 .lock()
469 .insert(split_0_channel.clone(), split_0_tx);
470 PULSAR_ACK_CHANNEL
471 .lock()
472 .insert(split_1_channel.clone(), split_1_tx);
473 PULSAR_ACK_CHANNEL
474 .lock()
475 .insert(empty_split_channel.clone(), empty_split_tx);
476
477 WaitCheckpointTask::AckPulsarMessage(vec![
478 (split_0_channel.clone(), message_ids([Some(b"split-0-old")])),
479 (
480 split_1_channel.clone(),
481 message_ids([Some(b"split-1-latest"), None]),
482 ),
483 (
484 split_0_channel.clone(),
485 message_ids([Some(b"split-0-latest")]),
486 ),
487 (split_0_channel.clone(), message_ids([None])),
488 (empty_split_channel.clone(), message_ids([None, None])),
489 ])
490 .run(SourceId::new(26891), "test_pulsar_source")
491 .await;
492
493 assert_eq!(split_0_rx.try_recv().unwrap(), b"split-0-latest");
494 assert_eq!(split_0_rx.try_recv(), Err(TryRecvError::Empty));
495 assert_eq!(split_1_rx.try_recv().unwrap(), b"split-1-latest");
496 assert_eq!(split_1_rx.try_recv(), Err(TryRecvError::Empty));
497 assert_eq!(empty_split_rx.try_recv(), Err(TryRecvError::Empty));
498
499 let mut ack_channels = PULSAR_ACK_CHANNEL.lock();
500 ack_channels.remove(&split_0_channel);
501 ack_channels.remove(&split_1_channel);
502 ack_channels.remove(&empty_split_channel);
503 }
504}