1use risingwave_connector::source::ADBC_SNOWFLAKE_CONNECTOR;
18
19use super::*;
20
21mod json;
22use json::*;
23mod avro;
24use avro::extract_avro_table_schema;
25pub mod debezium;
26pub mod iceberg;
27use iceberg::extract_iceberg_columns;
28mod protobuf;
29use protobuf::extract_protobuf_table_schema;
30pub mod nexmark;
31
32macro_rules! feature_gated_function {
37 (
38 mod $mod_name:ident,
39 $feature_name:literal,
40 async fn $func_name:ident ( $( $param_name:ident : $param_type:ty ),* $(,)? ) -> $ret_type:ty
41 ) => {
42 #[cfg(feature = $feature_name)]
43 pub mod $mod_name;
44 #[cfg(feature = $feature_name)]
45 use $mod_name::$func_name;
46
47 #[cfg(not(feature = $feature_name))]
48 #[allow(unused_variables)]
49 pub async fn $func_name( $( $param_name : $param_type ),* ) -> $ret_type {
50 Err(anyhow::anyhow!(
51 "Feature `{}` is not enabled at compile time. \
52 Please enable it in `Cargo.toml` and rebuild.",
53 $feature_name
54 ))
55 }
56 };
57}
58
59feature_gated_function!(
60 mod adbc_snowflake,
61 "source-adbc_snowflake",
62 async fn extract_adbc_snowflake_columns(
63 with_properties: &WithOptionsSecResolved,
64 ) -> anyhow::Result<Vec<ColumnCatalog>>
65);
66
67pub async fn bind_columns_from_source(
73 session: &SessionImpl,
74 format_encode: &FormatEncodeOptions,
75 with_properties: Either<&WithOptions, &WithOptionsSecResolved>,
76 create_source_type: CreateSourceType,
77) -> Result<(Option<Vec<ColumnCatalog>>, StreamSourceInfo)> {
78 let (columns_from_resolve_source, mut source_info) =
79 if create_source_type == CreateSourceType::SharedCdc {
80 bind_columns_from_source_for_cdc(session, format_encode)?
81 } else {
82 bind_columns_from_source_for_non_cdc(session, format_encode, with_properties).await?
83 };
84 if create_source_type.is_shared() {
85 source_info.cdc_source_job = true;
87 source_info.is_distributed = create_source_type == CreateSourceType::SharedNonCdc;
88 }
89 Ok((columns_from_resolve_source, source_info))
90}
91
92async fn bind_columns_from_source_for_non_cdc(
93 session: &SessionImpl,
94 format_encode: &FormatEncodeOptions,
95 with_properties: Either<&WithOptions, &WithOptionsSecResolved>,
96) -> Result<(Option<Vec<ColumnCatalog>>, StreamSourceInfo)> {
97 const MESSAGE_NAME_KEY: &str = "message";
98 const KEY_MESSAGE_NAME_KEY: &str = "key.message";
99 const NAME_STRATEGY_KEY: &str = "schema.registry.name.strategy";
100
101 let options_with_secret = match with_properties {
102 Either::Left(options) => {
103 let (sec_resolve_props, connection_type, _) = resolve_connection_ref_and_secret_ref(
104 options.clone(),
105 session,
106 Some(TelemetryDatabaseObject::Source),
107 )?;
108 if !SOURCE_ALLOWED_CONNECTION_CONNECTOR.contains(&connection_type) {
109 return Err(RwError::from(ProtocolError(format!(
110 "connection type {:?} is not allowed, allowed types: {:?}",
111 connection_type, SOURCE_ALLOWED_CONNECTION_CONNECTOR
112 ))));
113 }
114
115 sec_resolve_props
116 }
117 Either::Right(options_with_secret) => options_with_secret.clone(),
118 };
119
120 let is_kafka: bool = options_with_secret.is_kafka_connector();
121
122 let (sec_resolve_props, connection_type, schema_registry_conn_ref) =
124 resolve_connection_ref_and_secret_ref(
125 WithOptions::try_from(format_encode.row_options())?,
126 session,
127 Some(TelemetryDatabaseObject::Source),
128 )?;
129 ensure_connection_type_allowed(connection_type, &SOURCE_ALLOWED_CONNECTION_SCHEMA_REGISTRY)?;
130
131 let (format_encode_options, format_encode_secret_refs) = sec_resolve_props.into_parts();
132 let mut format_encode_options_to_consume = LocalSecretManager::global().fill_secrets(
134 format_encode_options.clone(),
135 format_encode_secret_refs.clone(),
136 )?;
137
138 fn get_key_message_name(options: &mut BTreeMap<String, String>) -> Option<String> {
139 consume_string_from_options(options, KEY_MESSAGE_NAME_KEY)
140 .map(|ele| Some(ele.0))
141 .unwrap_or(None)
142 }
143 fn get_sr_name_strategy_check(
144 options: &mut BTreeMap<String, String>,
145 use_sr: bool,
146 ) -> Result<Option<i32>> {
147 let name_strategy = get_name_strategy_or_default(try_consume_string_from_options(
148 options,
149 NAME_STRATEGY_KEY,
150 ))?;
151 if !use_sr && name_strategy.is_some() {
152 return Err(RwError::from(ProtocolError(
153 "schema registry name strategy only works with schema registry enabled".to_owned(),
154 )));
155 }
156 Ok(name_strategy)
157 }
158
159 let mut stream_source_info = StreamSourceInfo {
160 format: format_to_prost(&format_encode.format) as i32,
161 row_encode: row_encode_to_prost(&format_encode.row_encode) as i32,
162 format_encode_options,
163 format_encode_secret_refs,
164 connection_id: schema_registry_conn_ref,
165 ..Default::default()
166 };
167
168 if format_encode.format == Format::Debezium {
169 try_consume_string_from_options(&mut format_encode_options_to_consume, DEBEZIUM_IGNORE_KEY);
170 }
171
172 let columns = match (&format_encode.format, &format_encode.row_encode) {
173 (Format::Native, Encode::Native)
174 | (Format::Plain, Encode::Bytes)
175 | (Format::DebeziumMongo, Encode::Json) => None,
176 (Format::Plain, Encode::Protobuf) | (Format::Upsert, Encode::Protobuf) => {
177 let (row_schema_location, use_schema_registry) =
178 get_schema_location(&mut format_encode_options_to_consume)?;
179 let message_name = consume_string_from_options(
180 &mut format_encode_options_to_consume,
181 MESSAGE_NAME_KEY,
182 )?;
183 let name_strategy = get_sr_name_strategy_check(
184 &mut format_encode_options_to_consume,
185 use_schema_registry,
186 )?;
187
188 stream_source_info.use_schema_registry = use_schema_registry;
189 stream_source_info
190 .row_schema_location
191 .clone_from(&row_schema_location.0);
192 stream_source_info
193 .proto_message_name
194 .clone_from(&message_name.0);
195 stream_source_info.key_message_name =
196 get_key_message_name(&mut format_encode_options_to_consume);
197 stream_source_info.name_strategy =
198 name_strategy.unwrap_or(PbSchemaRegistryNameStrategy::Unspecified as i32);
199
200 Some(
201 extract_protobuf_table_schema(
202 &stream_source_info,
203 &options_with_secret,
204 &mut format_encode_options_to_consume,
205 )
206 .await?,
207 )
208 }
209 (format @ (Format::Plain | Format::Upsert | Format::Debezium), Encode::Avro) => {
210 if format_encode_options_to_consume.contains_key(PULSAR_SCHEMA_URL_KEY) {
211 } else if format_encode_options_to_consume
215 .remove(AWS_GLUE_SCHEMA_ARN_KEY)
216 .is_none()
217 {
218 let (row_schema_location, use_schema_registry) =
223 get_schema_location(&mut format_encode_options_to_consume)?;
224
225 if matches!(format, Format::Debezium) && !use_schema_registry {
226 return Err(RwError::from(ProtocolError(
227 "schema location for DEBEZIUM_AVRO row format is not supported".to_owned(),
228 )));
229 }
230
231 let message_name = try_consume_string_from_options(
232 &mut format_encode_options_to_consume,
233 MESSAGE_NAME_KEY,
234 );
235 let name_strategy = get_sr_name_strategy_check(
236 &mut format_encode_options_to_consume,
237 use_schema_registry,
238 )?;
239
240 stream_source_info.use_schema_registry = use_schema_registry;
241 stream_source_info
242 .row_schema_location
243 .clone_from(&row_schema_location.0);
244 stream_source_info.proto_message_name =
245 message_name.unwrap_or(AstString("".into())).0;
246 stream_source_info.key_message_name =
247 get_key_message_name(&mut format_encode_options_to_consume);
248 stream_source_info.name_strategy =
249 name_strategy.unwrap_or(PbSchemaRegistryNameStrategy::Unspecified as i32);
250 }
251
252 Some(
253 extract_avro_table_schema(
254 &stream_source_info,
255 &options_with_secret,
256 &mut format_encode_options_to_consume,
257 matches!(format, Format::Debezium),
258 )
259 .await?,
260 )
261 }
262 (Format::Plain, Encode::Csv) => {
263 let chars =
264 consume_string_from_options(&mut format_encode_options_to_consume, "delimiter")?.0;
265 let delimiter = get_delimiter(chars.as_str()).context("failed to parse delimiter")?;
266 let has_header = try_consume_string_from_options(
267 &mut format_encode_options_to_consume,
268 "without_header",
269 )
270 .map(|s| s.0 == "false")
271 .unwrap_or(true);
272
273 if is_kafka && has_header {
274 return Err(RwError::from(ProtocolError(
275 "CSV HEADER is not supported when creating table with Kafka connector"
276 .to_owned(),
277 )));
278 }
279
280 stream_source_info.csv_delimiter = delimiter as i32;
281 stream_source_info.csv_has_header = has_header;
282
283 None
284 }
285 (Format::Plain, Encode::Parquet) => None,
287 (
288 Format::Plain | Format::Upsert | Format::Maxwell | Format::Canal | Format::Debezium,
289 Encode::Json,
290 ) => {
291 if matches!(
292 format_encode.format,
293 Format::Plain | Format::Upsert | Format::Debezium
294 ) {
295 if let Some(value) =
300 format_encode_options_to_consume.get(TimestamptzHandling::OPTION_KEY)
301 {
302 TimestamptzHandling::from_options(value)
303 .map_err(|err| InvalidInputSyntax(err.message))?;
304 }
305 try_consume_string_from_options(
306 &mut format_encode_options_to_consume,
307 TimestamptzHandling::OPTION_KEY,
308 );
309 }
310
311 if let Some(config) =
312 PulsarSchemaConfig::from_options(&format_encode_options_to_consume)?
313 {
314 Some(
315 extract_pulsar_json_table_schema(
316 &config,
317 &options_with_secret,
318 &mut format_encode_options_to_consume,
319 )
320 .await?,
321 )
322 } else {
323 let schema_config =
324 get_json_schema_location(&mut format_encode_options_to_consume)?;
325 stream_source_info.use_schema_registry =
326 json_schema_infer_use_schema_registry(&schema_config);
327
328 extract_json_table_schema(
329 &schema_config,
330 &options_with_secret,
331 &mut format_encode_options_to_consume,
332 )
333 .await?
334 }
335 }
336 (Format::None, Encode::None) => {
337 if options_with_secret.is_iceberg_connector() {
338 Some(
339 extract_iceberg_columns(&options_with_secret)
340 .await
341 .map_err(|err| ProtocolError(err.to_report_string()))?,
342 )
343 } else if options_with_secret
344 .get(UPSTREAM_SOURCE_KEY)
345 .is_some_and(|s| s.eq_ignore_ascii_case(ADBC_SNOWFLAKE_CONNECTOR))
346 {
347 Some(
348 extract_adbc_snowflake_columns(&options_with_secret)
349 .await
350 .map_err(|err| ProtocolError(err.to_report_string()))?,
351 )
352 } else {
353 None
354 }
355 }
356 (format, encoding) => {
357 return Err(RwError::from(ProtocolError(format!(
358 "Unknown combination {:?} {:?}",
359 format, encoding
360 ))));
361 }
362 };
363
364 if !format_encode_options_to_consume.is_empty() {
365 let err_string = format!(
366 "Get unknown format_encode_options for {:?} {:?}: {}",
367 format_encode.format,
368 format_encode.row_encode,
369 format_encode_options_to_consume
370 .keys()
371 .cloned()
372 .collect::<Vec<String>>()
373 .join(","),
374 );
375 session.notice_to_user(err_string);
376 }
377 Ok((columns, stream_source_info))
378}
379
380fn bind_columns_from_source_for_cdc(
381 session: &SessionImpl,
382 format_encode: &FormatEncodeOptions,
383) -> Result<(Option<Vec<ColumnCatalog>>, StreamSourceInfo)> {
384 let with_options = WithOptions::try_from(format_encode.row_options())?;
385 if !with_options.connection_ref().is_empty() {
386 return Err(RwError::from(NotSupported(
387 "CDC connector does not support connection ref yet".to_owned(),
388 "Explicitly specify the connection in WITH clause".to_owned(),
389 )));
390 }
391 let (format_encode_options, format_encode_secret_refs) =
392 resolve_secret_ref_in_with_options(with_options, session)?.into_parts();
393
394 let mut format_encode_options_to_consume = LocalSecretManager::global().fill_secrets(
396 format_encode_options.clone(),
397 format_encode_secret_refs.clone(),
398 )?;
399
400 match (&format_encode.format, &format_encode.row_encode) {
401 (Format::Plain, Encode::Json) => (),
402 (format, encoding) => {
403 return Err(RwError::from(ProtocolError(format!(
405 "Row format for CDC connectors should be either omitted or set to `FORMAT PLAIN ENCODE JSON`, got: {:?} {:?}",
406 format, encoding
407 ))));
408 }
409 };
410
411 let columns = ColumnCatalog::debezium_cdc_source_cols().to_vec();
412 let schema_config = get_json_schema_location(&mut format_encode_options_to_consume)?;
413
414 let stream_source_info = StreamSourceInfo {
415 format: format_to_prost(&format_encode.format) as i32,
416 row_encode: row_encode_to_prost(&format_encode.row_encode) as i32,
417 format_encode_options,
418 use_schema_registry: json_schema_infer_use_schema_registry(&schema_config),
419 cdc_source_job: true,
420 is_distributed: false,
421 format_encode_secret_refs,
422 ..Default::default()
423 };
424 if !format_encode_options_to_consume.is_empty() {
425 let err_string = format!(
426 "Get unknown format_encode_options for {:?} {:?}: {}",
427 format_encode.format,
428 format_encode.row_encode,
429 format_encode_options_to_consume
430 .keys()
431 .cloned()
432 .collect::<Vec<String>>()
433 .join(","),
434 );
435 session.notice_to_user(err_string);
436 }
437 Ok((Some(columns), stream_source_info))
438}
439
440pub(super) fn format_to_prost(format: &Format) -> FormatType {
441 match format {
442 Format::Native => FormatType::Native,
443 Format::Plain => FormatType::Plain,
444 Format::Upsert => FormatType::Upsert,
445 Format::Debezium => FormatType::Debezium,
446 Format::DebeziumMongo => FormatType::DebeziumMongo,
447 Format::Maxwell => FormatType::Maxwell,
448 Format::Canal => FormatType::Canal,
449 Format::None => FormatType::None,
450 }
451}
452pub(super) fn row_encode_to_prost(row_encode: &Encode) -> EncodeType {
453 match row_encode {
454 Encode::Native => EncodeType::Native,
455 Encode::Json => EncodeType::Json,
456 Encode::Avro => EncodeType::Avro,
457 Encode::Protobuf => EncodeType::Protobuf,
458 Encode::Csv => EncodeType::Csv,
459 Encode::Bytes => EncodeType::Bytes,
460 Encode::Template => EncodeType::Template,
461 Encode::Parquet => EncodeType::Parquet,
462 Encode::None => EncodeType::None,
463 Encode::Text => EncodeType::Text,
464 }
465}
466
467pub fn get_schema_location(
468 format_encode_options: &mut BTreeMap<String, String>,
469) -> Result<(AstString, bool)> {
470 let schema_location = try_consume_string_from_options(format_encode_options, "schema.location");
471 let schema_registry = try_consume_string_from_options(format_encode_options, "schema.registry");
472 match (schema_location, schema_registry) {
473 (None, None) => Err(RwError::from(ProtocolError(
474 "missing either a schema location or a schema registry".to_owned(),
475 ))),
476 (None, Some(schema_registry)) => Ok((schema_registry, true)),
477 (Some(schema_location), None) => Ok((schema_location, false)),
478 (Some(_), Some(_)) => Err(RwError::from(ProtocolError(
479 "only need either the schema location or the schema registry".to_owned(),
480 ))),
481 }
482}
483
484pub fn schema_has_schema_registry(schema: &FormatEncodeOptions) -> bool {
485 match schema.row_encode {
486 Encode::Avro | Encode::Protobuf => true,
487 Encode::Json => {
488 let mut options = WithOptions::try_from(schema.row_options()).unwrap();
489 uses_pulsar_schema(&options, options.secret_ref())
490 || matches!(get_json_schema_location(options.inner_mut()), Ok(Some(_)))
491 }
492 _ => false,
493 }
494}
495
496#[inline]
497fn get_name_strategy_or_default(name_strategy: Option<AstString>) -> Result<Option<i32>> {
498 match name_strategy {
499 None => Ok(None),
500 Some(name) => Ok(Some(name_strategy_from_str(name.0.as_str())
501 .ok_or_else(|| RwError::from(ProtocolError(format!("\
502 expect strategy name in topic_name_strategy, record_name_strategy and topic_record_name_strategy, but got {}", name))))? as i32)),
503 }
504}