1use std::collections::{BTreeMap, HashMap};
16
17use anyhow::anyhow;
18use async_trait::async_trait;
19use phf::{Set, phf_set};
20use risingwave_common::array::{Op, RowRef, StreamChunk};
21use risingwave_common::catalog::Schema;
22use risingwave_common::row::{OwnedRow, Row};
23use risingwave_common::types::{DataType, Decimal};
24use serde::Deserialize;
25use serde_with::{DisplayFromStr, serde_as};
26use simd_json::prelude::ArrayTrait;
27use tiberius::numeric::Numeric;
28use tiberius::{AuthMethod, Client, ColumnData, Config, Query};
29use tokio::net::TcpStream;
30use with_options::WithOptions;
31
32use super::{
33 SINK_TYPE_APPEND_ONLY, SINK_TYPE_OPTION, SINK_TYPE_UPSERT, SinkError, SinkWriterMetrics,
34};
35use crate::connector_common::sql_server::create_mssql_client_with_config;
36use crate::enforce_secret::EnforceSecret;
37use crate::sink::writer::{LogSinkerOf, SinkWriter, SinkWriterExt};
38use crate::sink::{Result, Sink, SinkParam, SinkWriterParam};
39
40pub const SQLSERVER_SINK: &str = "sqlserver";
41
42fn default_max_batch_rows() -> usize {
43 1024
44}
45
46#[serde_as]
47#[derive(Clone, Debug, Deserialize, WithOptions)]
48pub struct SqlServerConfig {
49 #[serde(rename = "sqlserver.host")]
50 pub host: String,
51 #[serde(rename = "sqlserver.port")]
52 #[serde_as(as = "DisplayFromStr")]
53 pub port: u16,
54 #[serde(rename = "sqlserver.user")]
55 pub user: String,
56 #[serde(rename = "sqlserver.password")]
57 pub password: String,
58 #[serde(rename = "sqlserver.database")]
59 pub database: String,
60 #[serde(rename = "sqlserver.schema", default = "sql_server_default_schema")]
61 pub schema: String,
62 #[serde(rename = "sqlserver.table")]
63 pub table: String,
64 #[serde(
65 rename = "sqlserver.max_batch_rows",
66 default = "default_max_batch_rows"
67 )]
68 #[serde_as(as = "DisplayFromStr")]
69 pub max_batch_rows: usize,
70 pub r#type: String, #[serde(flatten)]
73 pub unknown_fields: std::collections::HashMap<String, String>,
74}
75
76crate::impl_sink_unknown_fields!(SqlServerConfig);
77
78pub fn sql_server_default_schema() -> String {
79 "dbo".to_owned()
80}
81
82impl SqlServerConfig {
83 pub fn from_btreemap(properties: BTreeMap<String, String>) -> Result<Self> {
84 let config =
85 serde_json::from_value::<SqlServerConfig>(serde_json::to_value(properties).unwrap())
86 .map_err(|e| SinkError::Config(anyhow!(e)))?;
87 if config.r#type != SINK_TYPE_APPEND_ONLY && config.r#type != SINK_TYPE_UPSERT {
88 return Err(SinkError::Config(anyhow!(
89 "`{}` must be {}, or {}",
90 SINK_TYPE_OPTION,
91 SINK_TYPE_APPEND_ONLY,
92 SINK_TYPE_UPSERT
93 )));
94 }
95 Ok(config)
96 }
97
98 pub fn full_object_path(&self) -> String {
99 format!("[{}].[{}].[{}]", self.database, self.schema, self.table)
100 }
101}
102
103impl EnforceSecret for SqlServerConfig {
104 const ENFORCE_SECRET_PROPERTIES: Set<&'static str> = phf_set! {
105 "sqlserver.password"
106 };
107}
108#[derive(Debug)]
109pub struct SqlServerSink {
110 pub config: SqlServerConfig,
111 schema: Schema,
112 pk_indices: Vec<usize>,
113 is_append_only: bool,
114}
115
116struct SqlServerColumnMetadata {
117 name: String,
118 is_pk: bool,
119 data_type: String,
120}
121
122impl EnforceSecret for SqlServerSink {
123 fn enforce_secret<'a>(
124 prop_iter: impl Iterator<Item = &'a str>,
125 ) -> crate::sink::ConnectorResult<()> {
126 for prop in prop_iter {
127 SqlServerConfig::enforce_one(prop)?;
128 }
129 Ok(())
130 }
131}
132impl SqlServerSink {
133 pub fn new(
134 mut config: SqlServerConfig,
135 schema: Schema,
136 pk_indices: Vec<usize>,
137 is_append_only: bool,
138 ) -> Result<Self> {
139 const TIBERIUS_PARAM_MAX: usize = 2000;
141 let params_per_op = schema.fields().len();
142 let tiberius_max_batch_rows = if params_per_op == 0 {
143 config.max_batch_rows
144 } else {
145 ((TIBERIUS_PARAM_MAX as f64 / params_per_op as f64).floor()) as usize
146 };
147 if tiberius_max_batch_rows == 0 {
148 return Err(SinkError::SqlServer(anyhow!(format!(
149 "too many column {}",
150 params_per_op
151 ))));
152 }
153 config.max_batch_rows = std::cmp::min(config.max_batch_rows, tiberius_max_batch_rows);
154 Ok(Self {
155 config,
156 schema,
157 pk_indices,
158 is_append_only,
159 })
160 }
161}
162
163impl TryFrom<SinkParam> for SqlServerSink {
164 type Error = SinkError;
165
166 fn try_from(param: SinkParam) -> std::result::Result<Self, Self::Error> {
167 let schema = param.schema();
168 let pk_indices = param.downstream_pk_or_empty();
169 let config = SqlServerConfig::from_btreemap(param.properties)?;
170 SqlServerSink::new(config, schema, pk_indices, param.sink_type.is_append_only())
171 }
172}
173
174impl Sink for SqlServerSink {
175 type LogSinker = LogSinkerOf<SqlServerSinkWriter>;
176
177 const SINK_NAME: &'static str = SQLSERVER_SINK;
178
179 crate::impl_validate_sink_unknown_fields!();
180
181 async fn validate(&self) -> Result<()> {
182 risingwave_common::license::Feature::SqlServerSink
183 .check_available()
184 .map_err(|e| anyhow::anyhow!(e))?;
185
186 if !self.is_append_only && self.pk_indices.is_empty() {
187 return Err(SinkError::Config(anyhow!(
188 "Primary key not defined for upsert SQL Server sink (please define in `primary_key` field)"
189 )));
190 }
191
192 for f in self.schema.fields() {
193 check_data_type_compatibility(&f.data_type)?;
194 }
195
196 let mut sql_client = SqlServerClient::new(&self.config).await?;
197 validate_sql_server_write_permission(&mut sql_client, &self.config, self.is_append_only)
198 .await?;
199 let sql_server_table_metadata =
200 query_sql_server_table_metadata(&mut sql_client, &self.config).await?;
201 let sql_server_pk_count = sql_server_table_metadata
202 .iter()
203 .filter(|metadata| metadata.is_pk)
204 .count();
205 let sql_server_table_metadata = sql_server_table_metadata
206 .into_iter()
207 .map(|metadata| (metadata.name.clone(), metadata))
208 .collect::<HashMap<_, _>>();
209
210 for (idx, col) in self.schema.fields().iter().enumerate() {
212 let rw_is_pk = self.pk_indices.contains(&idx);
213 match sql_server_table_metadata.get(&normalize_sql_server_column_name(&col.name)) {
214 None => {
215 return Err(SinkError::SqlServer(anyhow!(format!(
216 "column {} not found in the downstream SQL Server table {}",
217 col.name,
218 self.config.full_object_path()
219 ))));
220 }
221 Some(sql_server_col) => {
222 validate_data_type_compatibility(
223 &col.name,
224 &col.data_type,
225 &sql_server_col.data_type,
226 )?;
227 if self.is_append_only {
228 continue;
229 }
230 if rw_is_pk && !sql_server_col.is_pk {
231 return Err(SinkError::SqlServer(anyhow!(format!(
232 "column {} specified in primary_key mismatches with the downstream SQL Server table {} PK",
233 col.name,
234 self.config.full_object_path(),
235 ))));
236 }
237 if !rw_is_pk && sql_server_col.is_pk {
238 return Err(SinkError::SqlServer(anyhow!(format!(
239 "column {} unspecified in primary_key mismatches with the downstream SQL Server table {} PK",
240 col.name,
241 self.config.full_object_path(),
242 ))));
243 }
244 }
245 }
246 }
247
248 if !self.is_append_only && sql_server_pk_count != self.pk_indices.len() {
249 let sql_server_pk_columns = sql_server_table_metadata
250 .values()
251 .filter(|metadata| metadata.is_pk)
252 .map(|metadata| metadata.name.as_str())
253 .collect::<Vec<_>>()
254 .join(",");
255 let rw_pk_columns = self
256 .pk_indices
257 .iter()
258 .map(|idx| self.schema[*idx].name.as_str())
259 .collect::<Vec<_>>()
260 .join(",");
261 return Err(SinkError::SqlServer(anyhow!(format!(
262 "primary key does not match between RisingWave sink ({}: [{}]) and SQL Server table {} ({}: [{}])",
263 self.pk_indices.len(),
264 rw_pk_columns,
265 self.config.full_object_path(),
266 sql_server_pk_count,
267 sql_server_pk_columns,
268 ))));
269 }
270
271 Ok(())
272 }
273
274 async fn new_log_sinker(&self, writer_param: SinkWriterParam) -> Result<Self::LogSinker> {
275 Ok(SqlServerSinkWriter::new(
276 self.config.clone(),
277 self.schema.clone(),
278 self.pk_indices.clone(),
279 self.is_append_only,
280 )
281 .await?
282 .into_log_sinker(SinkWriterMetrics::new(&writer_param)))
283 }
284}
285
286enum SqlOp {
287 Insert(OwnedRow),
288 Merge(OwnedRow),
289 Delete(OwnedRow),
290}
291
292pub struct SqlServerSinkWriter {
293 config: SqlServerConfig,
294 schema: Schema,
295 pk_indices: Vec<usize>,
296 is_append_only: bool,
297 downstream_column_data_types: Vec<String>,
298 sql_client: SqlServerClient,
299 ops: Vec<SqlOp>,
300}
301
302impl SqlServerSinkWriter {
303 async fn new(
304 config: SqlServerConfig,
305 schema: Schema,
306 pk_indices: Vec<usize>,
307 is_append_only: bool,
308 ) -> Result<Self> {
309 let mut sql_client = SqlServerClient::new(&config).await?;
310 let downstream_column_data_types =
311 query_downstream_column_metadata(&mut sql_client, &config, &schema)
312 .await?
313 .into_iter()
314 .map(|metadata| metadata.data_type)
315 .collect();
316 let writer = Self {
317 config,
318 schema,
319 pk_indices,
320 is_append_only,
321 downstream_column_data_types,
322 sql_client,
323 ops: vec![],
324 };
325 Ok(writer)
326 }
327
328 async fn delete_one(&mut self, row: RowRef<'_>) -> Result<()> {
329 if self.ops.len() + 1 >= self.config.max_batch_rows {
330 self.flush().await?;
331 }
332 self.ops.push(SqlOp::Delete(row.into_owned_row()));
333 Ok(())
334 }
335
336 async fn upsert_one(&mut self, row: RowRef<'_>) -> Result<()> {
337 if self.ops.len() + 1 >= self.config.max_batch_rows {
338 self.flush().await?;
339 }
340 self.ops.push(SqlOp::Merge(row.into_owned_row()));
341 Ok(())
342 }
343
344 async fn insert_one(&mut self, row: RowRef<'_>) -> Result<()> {
345 if self.ops.len() + 1 >= self.config.max_batch_rows {
346 self.flush().await?;
347 }
348 self.ops.push(SqlOp::Insert(row.into_owned_row()));
349 Ok(())
350 }
351
352 async fn flush(&mut self) -> Result<()> {
353 use std::fmt::Write;
354 if self.ops.is_empty() {
355 return Ok(());
356 }
357 let mut query_str = String::new();
358 let col_num = self.schema.fields.len();
359 let mut next_param_id = 1;
360 let non_pk_col_indices = (0..col_num)
361 .filter(|idx| !self.pk_indices.contains(idx))
362 .collect::<Vec<usize>>();
363 let all_col_names = self
364 .schema
365 .fields
366 .iter()
367 .map(|f| format!("[{}]", f.name))
368 .collect::<Vec<_>>()
369 .join(",");
370 let all_source_col_names = self
371 .schema
372 .fields
373 .iter()
374 .map(|f| format!("[SOURCE].[{}]", f.name))
375 .collect::<Vec<_>>()
376 .join(",");
377 let pk_match = self
378 .pk_indices
379 .iter()
380 .map(|idx| {
381 format!(
382 "[SOURCE].[{}]=[TARGET].[{}]",
383 self.schema[*idx].name, self.schema[*idx].name
384 )
385 })
386 .collect::<Vec<_>>()
387 .join(" AND ");
388 let param_placeholders = |param_id: &mut usize| {
389 (0..col_num)
390 .map(|_| param_placeholder(param_id))
391 .collect::<Vec<_>>()
392 .join(",")
393 };
394 let set_all_source_col = non_pk_col_indices
395 .iter()
396 .map(|idx| {
397 format!(
398 "[{}]=[SOURCE].[{}]",
399 self.schema[*idx].name, self.schema[*idx].name
400 )
401 })
402 .collect::<Vec<_>>()
403 .join(",");
404 for op in &self.ops {
406 match op {
407 SqlOp::Insert(_) => {
408 write!(
409 &mut query_str,
410 "INSERT INTO {} ({}) VALUES ({});",
411 self.config.full_object_path(),
412 all_col_names,
413 param_placeholders(&mut next_param_id),
414 )
415 .unwrap();
416 }
417 SqlOp::Merge(_) => {
418 write!(
419 &mut query_str,
420 r#"MERGE {} WITH (HOLDLOCK) AS [TARGET]
421 USING (VALUES ({})) AS [SOURCE] ({})
422 ON {}
423 WHEN MATCHED THEN UPDATE SET {}
424 WHEN NOT MATCHED THEN INSERT ({}) VALUES ({});"#,
425 self.config.full_object_path(),
426 param_placeholders(&mut next_param_id),
427 all_col_names,
428 pk_match,
429 set_all_source_col,
430 all_col_names,
431 all_source_col_names,
432 )
433 .unwrap();
434 }
435 SqlOp::Delete(_) => {
436 write!(
437 &mut query_str,
438 r#"DELETE FROM {} WHERE {};"#,
439 self.config.full_object_path(),
440 self.pk_indices
441 .iter()
442 .map(|idx| {
443 let condition = format!(
444 "[{}]={}",
445 self.schema[*idx].name,
446 param_placeholder(&mut next_param_id)
447 );
448 condition
449 })
450 .collect::<Vec<_>>()
451 .join(" AND "),
452 )
453 .unwrap();
454 }
455 }
456 }
457
458 let mut query = Query::new(query_str);
459 for op in self.ops.drain(..) {
460 match op {
461 SqlOp::Insert(row) => {
462 bind_params(
463 &mut query,
464 row,
465 &self.schema,
466 &self.downstream_column_data_types,
467 0..col_num,
468 )?;
469 }
470 SqlOp::Merge(row) => {
471 bind_params(
472 &mut query,
473 row,
474 &self.schema,
475 &self.downstream_column_data_types,
476 0..col_num,
477 )?;
478 }
479 SqlOp::Delete(row) => {
480 bind_params(
481 &mut query,
482 row,
483 &self.schema,
484 &self.downstream_column_data_types,
485 self.pk_indices.iter().copied(),
486 )?;
487 }
488 }
489 }
490 query.execute(&mut self.sql_client.inner_client).await?;
491 Ok(())
492 }
493}
494
495#[async_trait]
496impl SinkWriter for SqlServerSinkWriter {
497 async fn begin_epoch(&mut self, _epoch: u64) -> Result<()> {
498 Ok(())
499 }
500
501 async fn write_batch(&mut self, chunk: StreamChunk) -> Result<()> {
502 for (op, row) in chunk.rows() {
503 match op {
504 Op::Insert => {
505 if self.is_append_only {
506 self.insert_one(row).await?;
507 } else {
508 self.upsert_one(row).await?;
509 }
510 }
511 Op::UpdateInsert => {
512 debug_assert!(!self.is_append_only);
513 self.upsert_one(row).await?;
514 }
515 Op::Delete => {
516 debug_assert!(!self.is_append_only);
517 self.delete_one(row).await?;
518 }
519 Op::UpdateDelete => {}
520 }
521 }
522 Ok(())
523 }
524
525 async fn barrier(&mut self, is_checkpoint: bool) -> Result<Self::CommitMetadata> {
526 if is_checkpoint {
527 self.flush().await?;
528 }
529 Ok(())
530 }
531}
532
533#[derive(Debug)]
534pub struct SqlServerClient {
535 pub inner_client: Client<tokio_util::compat::Compat<TcpStream>>,
536}
537
538impl SqlServerClient {
539 async fn new(msconfig: &SqlServerConfig) -> Result<Self> {
540 let mut config = Config::new();
541 config.host(&msconfig.host);
542 config.port(msconfig.port);
543 config.authentication(AuthMethod::sql_server(&msconfig.user, &msconfig.password));
544 config.database(&msconfig.database);
545 config.trust_cert();
546 Self::new_with_config(config).await
547 }
548
549 pub async fn new_with_config(config: Config) -> Result<Self> {
550 let inner_client = create_mssql_client_with_config(config)
551 .await
552 .map_err(SinkError::SqlServer)?;
553 Ok(Self { inner_client })
554 }
555}
556
557async fn query_sql_server_table_metadata(
558 sql_client: &mut SqlServerClient,
559 config: &SqlServerConfig,
560) -> Result<Vec<SqlServerColumnMetadata>> {
561 let mut sql_server_table_metadata = Vec::new();
562 let query_table_metadata_error = || {
563 SinkError::SqlServer(anyhow!(format!(
564 "SQL Server table {} metadata error",
565 config.full_object_path()
566 )))
567 };
568 static QUERY_TABLE_METADATA: &str = r#"
574SELECT
575 col.name AS ColumnName,
576 CAST(CASE WHEN pk_col.column_id IS NULL THEN 0 ELSE 1 END AS int) AS IsPk,
577 typ.name AS DataType
578FROM
579 sys.columns col
580JOIN
581 sys.types typ ON typ.user_type_id = col.user_type_id
582LEFT JOIN
583 (
584 SELECT ic.object_id, ic.column_id
585 FROM sys.indexes pk
586 JOIN sys.index_columns ic ON ic.object_id = pk.object_id AND ic.index_id = pk.index_id
587 WHERE pk.is_primary_key = 1
588 ) pk_col ON pk_col.object_id = col.object_id AND pk_col.column_id = col.column_id
589WHERE
590 col.object_id = OBJECT_ID(@P1)
591ORDER BY
592 col.column_id;"#;
593 let rows = sql_client
594 .inner_client
595 .query(QUERY_TABLE_METADATA, &[&config.full_object_path()])
596 .await?
597 .into_results()
598 .await?;
599 for row in rows.into_iter().flatten() {
600 let mut iter = row.into_iter();
601 let ColumnData::String(Some(col_name)) =
602 iter.next().ok_or_else(query_table_metadata_error)?
603 else {
604 return Err(query_table_metadata_error());
605 };
606 let ColumnData::I32(Some(col_is_pk)) =
607 iter.next().ok_or_else(query_table_metadata_error)?
608 else {
609 return Err(query_table_metadata_error());
610 };
611 let ColumnData::String(Some(data_type)) =
612 iter.next().ok_or_else(query_table_metadata_error)?
613 else {
614 return Err(query_table_metadata_error());
615 };
616 sql_server_table_metadata.push(SqlServerColumnMetadata {
617 name: normalize_sql_server_column_name(&col_name),
618 is_pk: col_is_pk != 0,
619 data_type: data_type.into_owned(),
620 });
621 }
622 Ok(sql_server_table_metadata)
623}
624
625async fn validate_sql_server_write_permission(
626 sql_client: &mut SqlServerClient,
627 config: &SqlServerConfig,
628 is_append_only: bool,
629) -> Result<()> {
630 let permission_query_error = || {
631 SinkError::SqlServer(anyhow!(format!(
632 "SQL Server table {} permission metadata error",
633 config.full_object_path()
634 )))
635 };
636 static QUERY_WRITE_PERMISSION: &str = r#"
637SELECT
638 CAST(HAS_PERMS_BY_NAME(@P1, 'OBJECT', 'INSERT') AS int) AS CanInsert,
639 CAST(HAS_PERMS_BY_NAME(@P1, 'OBJECT', 'UPDATE') AS int) AS CanUpdate,
640 CAST(HAS_PERMS_BY_NAME(@P1, 'OBJECT', 'DELETE') AS int) AS CanDelete;"#;
641 let rows = sql_client
642 .inner_client
643 .query(QUERY_WRITE_PERMISSION, &[&config.full_object_path()])
644 .await?
645 .into_results()
646 .await?;
647 let mut rows = rows.into_iter().flatten();
648 let row = rows.next().ok_or_else(permission_query_error)?;
649 let mut iter = row.into_iter();
650 let ColumnData::I32(can_insert) = iter.next().ok_or_else(permission_query_error)? else {
651 return Err(permission_query_error());
652 };
653 let ColumnData::I32(can_update) = iter.next().ok_or_else(permission_query_error)? else {
654 return Err(permission_query_error());
655 };
656 let ColumnData::I32(can_delete) = iter.next().ok_or_else(permission_query_error)? else {
657 return Err(permission_query_error());
658 };
659
660 let missing_permissions = missing_sql_server_write_permissions(
661 is_append_only,
662 permission_is_granted(can_insert),
663 permission_is_granted(can_update),
664 permission_is_granted(can_delete),
665 );
666 if missing_permissions.is_empty() {
667 return Ok(());
668 }
669
670 Err(SinkError::SqlServer(anyhow!(format!(
671 "SQL Server user {} lacks required write permission(s) {} on table {}",
672 config.user,
673 missing_permissions.join(", "),
674 config.full_object_path()
675 ))))
676}
677
678fn permission_is_granted(permission_value: Option<i32>) -> bool {
679 permission_value == Some(1)
680}
681
682fn missing_sql_server_write_permissions(
683 is_append_only: bool,
684 can_insert: bool,
685 can_update: bool,
686 can_delete: bool,
687) -> Vec<&'static str> {
688 let mut missing_permissions = vec![];
689 if !can_insert {
690 missing_permissions.push("INSERT");
691 }
692 if !is_append_only {
693 if !can_update {
694 missing_permissions.push("UPDATE");
695 }
696 if !can_delete {
697 missing_permissions.push("DELETE");
698 }
699 }
700 missing_permissions
701}
702
703async fn query_downstream_column_metadata(
704 sql_client: &mut SqlServerClient,
705 config: &SqlServerConfig,
706 schema: &Schema,
707) -> Result<Vec<SqlServerColumnMetadata>> {
708 let sql_server_table_metadata = query_sql_server_table_metadata(sql_client, config)
709 .await?
710 .into_iter()
711 .map(|metadata| (metadata.name.clone(), metadata))
712 .collect::<HashMap<_, _>>();
713 schema
714 .fields()
715 .iter()
716 .map(|col| {
717 sql_server_table_metadata
718 .get(&normalize_sql_server_column_name(&col.name))
719 .map(|metadata| SqlServerColumnMetadata {
720 name: metadata.name.clone(),
721 is_pk: metadata.is_pk,
722 data_type: metadata.data_type.clone(),
723 })
724 .ok_or_else(|| {
725 SinkError::SqlServer(anyhow!(format!(
726 "column {} not found in the downstream SQL Server table {}",
727 col.name,
728 config.full_object_path()
729 )))
730 })
731 })
732 .collect()
733}
734
735fn param_placeholder(param_id: &mut usize) -> String {
736 let placeholder = format!("@P{}", *param_id);
737 *param_id += 1;
738 placeholder
739}
740
741fn bind_params(
742 query: &mut Query<'_>,
743 row: impl Row,
744 schema: &Schema,
745 downstream_column_data_types: &[String],
746 col_indices: impl Iterator<Item = usize>,
747) -> Result<()> {
748 use risingwave_common::types::ScalarRefImpl;
749 for col_idx in col_indices {
750 match row.datum_at(col_idx) {
751 Some(data_ref) => match data_ref {
752 ScalarRefImpl::Int16(v) => query.bind(v),
753 ScalarRefImpl::Int32(v) => query.bind(v),
754 ScalarRefImpl::Int64(v) => query.bind(v),
755 ScalarRefImpl::Float32(v) => query.bind(v.into_inner()),
756 ScalarRefImpl::Float64(v) => query.bind(v.into_inner()),
757 ScalarRefImpl::Utf8(v) => query.bind(v.to_owned()),
758 ScalarRefImpl::Bool(v) => query.bind(v),
759 ScalarRefImpl::Decimal(v) => match v {
760 Decimal::Normalized(d) => {
761 query.bind(decimal_to_sql(&d));
762 }
763 Decimal::NaN | Decimal::PositiveInf | Decimal::NegativeInf => {
764 tracing::warn!(
765 "Inf, -Inf, Nan in RisingWave decimal is converted into SQL Server null!"
766 );
767 query.bind(None as Option<Numeric>);
768 }
769 },
770 ScalarRefImpl::Date(v) => query.bind(v.0),
771 ScalarRefImpl::Timestamp(v) => query.bind(v.0),
772 ScalarRefImpl::Timestamptz(v) => {
773 let downstream_data_type = &downstream_column_data_types[col_idx];
774 match downstream_data_type.as_str() {
775 "bigint" | "int" | "smallint" | "tinyint" => {
776 query.bind(v.timestamp_micros());
777 }
778 "datetimeoffset" => {
779 query.bind(v.to_datetime_utc().fixed_offset());
780 }
781 "datetime" | "datetime2" | "smalldatetime" => {
782 query.bind(v.to_datetime_utc().naive_utc());
783 }
784 _ => {
785 return Err(unexpected_downstream_timestamptz_type(
786 downstream_data_type,
787 ));
788 }
789 };
790 }
791 ScalarRefImpl::Time(v) => query.bind(v.0),
792 ScalarRefImpl::Bytea(v) => query.bind(v.to_vec()),
793 ScalarRefImpl::Interval(_) => return Err(data_type_not_supported("Interval")),
794 ScalarRefImpl::Jsonb(_) => return Err(data_type_not_supported("Jsonb")),
795 ScalarRefImpl::Variant(_) => return Err(data_type_not_supported("Variant")),
796 ScalarRefImpl::Struct(_) => return Err(data_type_not_supported("Struct")),
797 ScalarRefImpl::List(_) => return Err(data_type_not_supported("List")),
798 ScalarRefImpl::Int256(_) => return Err(data_type_not_supported("Int256")),
799 ScalarRefImpl::Serial(_) => return Err(data_type_not_supported("Serial")),
800 ScalarRefImpl::Map(_) => return Err(data_type_not_supported("Map")),
801 ScalarRefImpl::Vector(_) => return Err(data_type_not_supported("Vector")),
802 },
803 None => match schema[col_idx].data_type {
804 DataType::Boolean => {
805 query.bind(None as Option<bool>);
806 }
807 DataType::Int16 => {
808 query.bind(None as Option<i16>);
809 }
810 DataType::Int32 => {
811 query.bind(None as Option<i32>);
812 }
813 DataType::Int64 => {
814 query.bind(None as Option<i64>);
815 }
816 DataType::Float32 => {
817 query.bind(None as Option<f32>);
818 }
819 DataType::Float64 => {
820 query.bind(None as Option<f64>);
821 }
822 DataType::Decimal => {
823 query.bind(None as Option<Numeric>);
824 }
825 DataType::Date => {
826 query.bind(None as Option<chrono::NaiveDate>);
827 }
828 DataType::Time => {
829 query.bind(None as Option<chrono::NaiveTime>);
830 }
831 DataType::Timestamp => {
832 query.bind(None as Option<chrono::NaiveDateTime>);
833 }
834 DataType::Timestamptz => {
835 let downstream_data_type = &downstream_column_data_types[col_idx];
836 match downstream_data_type.as_str() {
837 "bigint" | "int" | "smallint" | "tinyint" => {
838 query.bind(None as Option<i64>);
839 }
840 "datetimeoffset" => {
841 query.bind(None as Option<chrono::DateTime<chrono::FixedOffset>>);
842 }
843 "datetime" | "datetime2" | "smalldatetime" => {
844 query.bind(None as Option<chrono::NaiveDateTime>);
845 }
846 _ => {
847 return Err(unexpected_downstream_timestamptz_type(
848 downstream_data_type,
849 ));
850 }
851 };
852 }
853 DataType::Varchar => {
854 query.bind(None as Option<String>);
855 }
856 DataType::Bytea => {
857 query.bind(None as Option<Vec<u8>>);
858 }
859 DataType::Interval => return Err(data_type_not_supported("Interval")),
860 DataType::Struct(_) => return Err(data_type_not_supported("Struct")),
861 DataType::List(_) => return Err(data_type_not_supported("List")),
862 DataType::Jsonb => return Err(data_type_not_supported("Jsonb")),
863 DataType::Variant => return Err(data_type_not_supported("Variant")),
864 DataType::Serial => return Err(data_type_not_supported("Serial")),
865 DataType::Int256 => return Err(data_type_not_supported("Int256")),
866 DataType::Map(_) => return Err(data_type_not_supported("Map")),
867 DataType::Vector(_) => return Err(data_type_not_supported("Vector")),
868 },
869 };
870 }
871 Ok(())
872}
873
874fn data_type_not_supported(data_type_name: &str) -> SinkError {
875 SinkError::SqlServer(anyhow!(format!(
876 "{data_type_name} is not supported in SQL Server"
877 )))
878}
879
880fn unexpected_downstream_timestamptz_type(sql_server_data_type: &str) -> SinkError {
881 SinkError::SqlServer(anyhow!(format!(
882 "unexpected downstream SQL Server type {sql_server_data_type} for Timestamptz"
883 )))
884}
885
886fn check_data_type_compatibility(data_type: &DataType) -> Result<()> {
887 match data_type {
888 DataType::Boolean
889 | DataType::Int16
890 | DataType::Int32
891 | DataType::Int64
892 | DataType::Float32
893 | DataType::Float64
894 | DataType::Decimal
895 | DataType::Date
896 | DataType::Varchar
897 | DataType::Time
898 | DataType::Timestamp
899 | DataType::Timestamptz
900 | DataType::Bytea => Ok(()),
901 DataType::Interval => Err(data_type_not_supported("Interval")),
902 DataType::Struct(_) => Err(data_type_not_supported("Struct")),
903 DataType::List(_) => Err(data_type_not_supported("List")),
904 DataType::Jsonb => Err(data_type_not_supported("Jsonb")),
905 DataType::Variant => Err(data_type_not_supported("Variant")),
906 DataType::Serial => Err(data_type_not_supported("Serial")),
907 DataType::Int256 => Err(data_type_not_supported("Int256")),
908 DataType::Map(_) => Err(data_type_not_supported("Map")),
909 DataType::Vector(_) => Err(data_type_not_supported("Vector")),
910 }
911}
912
913fn normalize_sql_server_column_name(column_name: &str) -> String {
914 column_name.to_lowercase()
917}
918
919fn validate_data_type_compatibility(
920 column_name: &str,
921 rw_data_type: &DataType,
922 sql_server_data_type: &str,
923) -> Result<()> {
924 if sql_server_data_type_is_compatible(rw_data_type, sql_server_data_type) {
925 return Ok(());
926 }
927
928 Err(SinkError::SqlServer(anyhow!(format!(
929 "column {} data type {:?} is incompatible with downstream SQL Server type {}",
930 column_name, rw_data_type, sql_server_data_type
931 ))))
932}
933
934fn sql_server_data_type_is_compatible(rw_data_type: &DataType, sql_server_data_type: &str) -> bool {
935 match rw_data_type {
936 DataType::Boolean => sql_server_data_type == "bit",
937 DataType::Int16 => matches!(sql_server_data_type, "smallint" | "int" | "bigint"),
938 DataType::Int32 => matches!(sql_server_data_type, "int" | "bigint"),
939 DataType::Int64 => sql_server_data_type == "bigint",
940 DataType::Float32 => matches!(sql_server_data_type, "real" | "float"),
941 DataType::Float64 => sql_server_data_type == "float",
942 DataType::Decimal => matches!(sql_server_data_type, "decimal" | "numeric"),
943 DataType::Date => sql_server_data_type == "date",
944 DataType::Varchar => matches!(
945 sql_server_data_type,
946 "char" | "nchar" | "varchar" | "nvarchar" | "text" | "ntext"
947 ),
948 DataType::Time => sql_server_data_type == "time",
949 DataType::Timestamp => {
950 matches!(
951 sql_server_data_type,
952 "datetime" | "datetime2" | "smalldatetime"
953 )
954 }
955 DataType::Timestamptz => matches!(
956 sql_server_data_type,
957 "datetimeoffset"
958 | "datetime"
959 | "datetime2"
960 | "smalldatetime"
961 | "bigint"
962 | "int"
963 | "smallint"
964 | "tinyint"
965 ),
966 DataType::Bytea => matches!(sql_server_data_type, "binary" | "varbinary" | "image"),
967 DataType::Interval
968 | DataType::Struct(_)
969 | DataType::List(_)
970 | DataType::Jsonb
971 | DataType::Variant
972 | DataType::Serial
973 | DataType::Int256
974 | DataType::Map(_)
975 | DataType::Vector(_) => false,
976 }
977}
978
979fn decimal_to_sql(decimal: &rust_decimal::Decimal) -> Numeric {
981 let unpacked = decimal.unpack();
982
983 let mut value = (((unpacked.hi as u128) << 64)
984 + ((unpacked.mid as u128) << 32)
985 + unpacked.lo as u128) as i128;
986
987 if decimal.is_sign_negative() {
988 value = -value;
989 }
990
991 Numeric::new_with_scale(value, decimal.scale() as u8)
992}
993
994#[cfg(test)]
995mod tests {
996 use super::*;
997
998 #[test]
999 fn test_normalize_sql_server_column_name() {
1000 assert_eq!(normalize_sql_server_column_name("EventDate"), "eventdate");
1001 }
1002
1003 #[test]
1004 fn test_sql_server_data_type_compatibility() {
1005 assert!(sql_server_data_type_is_compatible(
1006 &DataType::Int16,
1007 "smallint"
1008 ));
1009 assert!(sql_server_data_type_is_compatible(&DataType::Int16, "int"));
1010 assert!(!sql_server_data_type_is_compatible(
1011 &DataType::Int32,
1012 "smallint"
1013 ));
1014
1015 assert!(sql_server_data_type_is_compatible(
1016 &DataType::Timestamp,
1017 "datetime2"
1018 ));
1019 assert!(sql_server_data_type_is_compatible(
1020 &DataType::Timestamptz,
1021 "datetimeoffset"
1022 ));
1023 assert!(sql_server_data_type_is_compatible(
1024 &DataType::Timestamptz,
1025 "datetime2"
1026 ));
1027 assert!(sql_server_data_type_is_compatible(
1028 &DataType::Timestamptz,
1029 "bigint"
1030 ));
1031 assert!(sql_server_data_type_is_compatible(
1032 &DataType::Timestamptz,
1033 "int"
1034 ));
1035 assert!(!sql_server_data_type_is_compatible(
1036 &DataType::Timestamp,
1037 "datetimeoffset"
1038 ));
1039
1040 assert!(sql_server_data_type_is_compatible(
1041 &DataType::Varchar,
1042 "nvarchar"
1043 ));
1044 assert!(!sql_server_data_type_is_compatible(
1045 &DataType::Varchar,
1046 "uniqueidentifier"
1047 ));
1048 }
1049
1050 #[test]
1051 fn test_missing_sql_server_write_permissions() {
1052 assert_eq!(
1053 missing_sql_server_write_permissions(true, false, false, false),
1054 vec!["INSERT"]
1055 );
1056 assert!(missing_sql_server_write_permissions(true, true, false, false).is_empty());
1057 assert_eq!(
1058 missing_sql_server_write_permissions(false, false, false, false),
1059 vec!["INSERT", "UPDATE", "DELETE"]
1060 );
1061 assert_eq!(
1062 missing_sql_server_write_permissions(false, true, false, true),
1063 vec!["UPDATE"]
1064 );
1065 assert!(missing_sql_server_write_permissions(false, true, true, true).is_empty());
1066 }
1067}