risingwave_connector/source/reader/
reader.rs1use std::sync::Arc;
16
17use anyhow::Context;
18use async_nats::jetstream::consumer::AckPolicy;
19use futures::StreamExt;
20use futures::stream::pending;
21use futures_async_stream::try_stream;
22use itertools::Itertools;
23use risingwave_common::bail;
24use risingwave_common::catalog::ColumnId;
25use thiserror_ext::AsReport as _;
26
27use crate::error::ConnectorResult;
28use crate::parser::{CommonParserConfig, ParserConfig, SpecificParserConfig};
29use crate::source::cdc::{CdcSourceRuntimeFlags, CdcSourceType};
30use crate::source::filesystem::opendal_source::opendal_enumerator::OpendalEnumerator;
31use crate::source::filesystem::opendal_source::{
32 DEFAULT_REFRESH_INTERVAL_SEC, OpendalAzblob, OpendalGcs, OpendalPosixFs, OpendalS3,
33 OpendalSource,
34};
35use crate::source::filesystem::{FsPageItem, OpendalFsSplit};
36use crate::source::{
37 BoxSourceReaderEventStream, BoxTryStream, Column, ConnectorProperties, ConnectorState,
38 CreateSplitReaderOpt, CreateSplitReaderResult, SourceColumnDesc, SourceContext,
39 WaitCheckpointTask,
40};
41use crate::{WithOptionsSecResolved, dispatch_cdc_source_type};
42
43#[derive(Clone, Debug)]
44pub struct SourceReader {
45 pub config: ConnectorProperties,
46 pub columns: Vec<SourceColumnDesc>,
47 pub parser_config: SpecificParserConfig,
48 pub connector_message_buffer_size: usize,
49}
50
51impl SourceReader {
52 pub fn new(
53 properties: WithOptionsSecResolved,
54 columns: Vec<SourceColumnDesc>,
55 connector_message_buffer_size: usize,
56 parser_config: SpecificParserConfig,
57 ) -> ConnectorResult<Self> {
58 let config = ConnectorProperties::extract(properties, false)?;
59
60 Ok(Self {
61 config,
62 columns,
63 parser_config,
64 connector_message_buffer_size,
65 })
66 }
67
68 fn get_target_columns(
69 &self,
70 column_ids: Vec<ColumnId>,
71 ) -> ConnectorResult<Vec<SourceColumnDesc>> {
72 column_ids
73 .iter()
74 .map(|id| {
75 self.columns
76 .iter()
77 .find(|c| c.column_id == *id)
78 .with_context(|| {
79 format!("Failed to find column id: {} in source: {:?}", id, self)
80 })
81 .cloned()
82 })
83 .try_collect()
84 .map_err(Into::into)
85 }
86
87 pub fn get_source_list(&self) -> ConnectorResult<BoxTryStream<FsPageItem>> {
88 let config = self.config.clone();
89 let list_interval_sec: u64;
90 let get_list_interval_sec =
91 |interval: Option<u64>| -> u64 { interval.unwrap_or(DEFAULT_REFRESH_INTERVAL_SEC) };
92 match config {
93 ConnectorProperties::Gcs(prop) => {
94 list_interval_sec = get_list_interval_sec(prop.fs_common.refresh_interval_sec);
95 let lister: OpendalEnumerator<OpendalGcs> =
96 OpendalEnumerator::new_gcs_source(*prop)?;
97 Ok(build_opendal_fs_list_stream(lister, list_interval_sec))
98 }
99 ConnectorProperties::OpendalS3(prop) => {
100 list_interval_sec = get_list_interval_sec(prop.fs_common.refresh_interval_sec);
101 let lister: OpendalEnumerator<OpendalS3> = OpendalEnumerator::new_s3_source(
102 &prop.s3_properties,
103 prop.assume_role,
104 prop.fs_common.compression_format,
105 )?;
106 Ok(build_opendal_fs_list_stream(lister, list_interval_sec))
107 }
108 ConnectorProperties::Azblob(prop) => {
109 list_interval_sec = get_list_interval_sec(prop.fs_common.refresh_interval_sec);
110 let lister: OpendalEnumerator<OpendalAzblob> =
111 OpendalEnumerator::new_azblob_source(*prop)?;
112 Ok(build_opendal_fs_list_stream(lister, list_interval_sec))
113 }
114 ConnectorProperties::PosixFs(prop) => {
115 list_interval_sec = get_list_interval_sec(prop.fs_common.refresh_interval_sec);
116 let lister: OpendalEnumerator<OpendalPosixFs> =
117 OpendalEnumerator::new_posix_fs_source(*prop)?;
118 Ok(build_opendal_fs_list_stream(lister, list_interval_sec))
119 }
120 other => bail!("Unsupported source: {:?}", other),
121 }
122 }
123
124 pub async fn create_wait_checkpoint_task(&self) -> ConnectorResult<Option<WaitCheckpointTask>> {
126 if dispatch_cdc_source_type!(
127 CdcSourceType::from(&self.config),
128 T,
129 T::NEEDS_OFFSET_COMMIT_ON_CHECKPOINT,
130 false
131 ) {
132 return Ok(Some(WaitCheckpointTask::CommitCdcOffset(None)));
133 }
134 Ok(match &self.config {
135 ConnectorProperties::GooglePubsub(prop) => Some(WaitCheckpointTask::AckPubsubMessage(
136 prop.subscription_client().await?,
137 vec![],
138 )),
139 ConnectorProperties::Nats(prop) => {
140 match prop.nats_properties_consumer.get_ack_policy()? {
141 a @ AckPolicy::Explicit | a @ AckPolicy::All => {
142 Some(WaitCheckpointTask::AckNatsJetStream(
143 prop.common.build_context().await?,
144 vec![],
145 a,
146 ))
147 }
148 AckPolicy::None | AckPolicy::FlowControl => None,
149 }
150 }
151 ConnectorProperties::Pulsar(_) => Some(WaitCheckpointTask::AckPulsarMessage(vec![])),
152 _ => None,
153 })
154 }
155
156 pub async fn build_stream(
160 &self,
161 state: ConnectorState,
162 column_ids: Vec<ColumnId>,
163 source_ctx: Arc<SourceContext>,
164 seek_to_latest: bool,
165 ) -> ConnectorResult<(BoxSourceReaderEventStream, CreateSplitReaderResult)> {
166 let Some(splits) = state else {
167 return Ok((pending().boxed(), Default::default()));
168 };
169 let config = self.config.clone();
170 let columns = self.get_target_columns(column_ids)?;
171
172 let data_gen_columns = Some(
173 columns
174 .iter()
175 .map(|col| Column {
176 name: col.name.clone(),
177 data_type: col.data_type.clone(),
178 is_visible: col.is_visible(),
179 })
180 .collect_vec(),
181 );
182
183 let parser_config = ParserConfig {
184 specific: self.parser_config.clone(),
185 common: CommonParserConfig {
186 rw_columns: columns,
187 },
188 };
189
190 config
191 .create_split_reader(
192 splits,
193 parser_config,
194 source_ctx,
195 data_gen_columns,
196 CreateSplitReaderOpt {
197 seek_to_latest,
198 ..Default::default()
199 },
200 )
201 .await
202 }
203}
204
205#[try_stream(boxed, ok = FsPageItem, error = crate::error::ConnectorError)]
206async fn build_opendal_fs_list_stream<Src: OpendalSource>(
207 lister: OpendalEnumerator<Src>,
208 list_interval_sec: u64,
209) {
210 loop {
211 let matcher = lister.get_matcher();
212 let mut object_metadata_iter = lister.list().await?;
213 while let Some(list_res) = object_metadata_iter.next().await {
214 match list_res {
215 Ok(res) => {
216 if matcher
217 .as_ref()
218 .map(|m| m.matches(&res.name) || m.to_string() == res.name)
219 .unwrap_or(true)
220 {
221 yield res
222 } else {
223 continue;
224 }
225 }
226 Err(err) => {
227 tracing::error!(error = %err.as_report(), "list object fail");
228 return Err(err);
229 }
230 }
231 }
232 tokio::time::sleep(std::time::Duration::from_secs(list_interval_sec)).await;
233 }
234}
235
236#[try_stream(boxed, ok = OpendalFsSplit<Src>, error = crate::error::ConnectorError)]
237pub async fn build_opendal_fs_list_for_batch<Src: OpendalSource>(lister: OpendalEnumerator<Src>) {
238 let matcher = lister.get_matcher();
239 let mut object_metadata_iter = lister.list().await?;
240
241 while let Some(list_res) = object_metadata_iter.next().await {
242 match list_res {
243 Ok(res) => {
244 if matcher
245 .as_ref()
246 .map(|m| m.matches(&res.name))
247 .unwrap_or(true)
248 {
249 let split = OpendalFsSplit::new(res.name, 0, res.size as usize);
250 yield split
251 } else {
252 continue;
253 }
254 }
255 Err(err) => {
256 tracing::error!(error = %err.as_report(), "list object fail");
257 return Err(err);
258 }
259 }
260 }
261}