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