risingwave_connector/parser/
sql_server.rs1use std::collections::HashSet;
16use std::str::FromStr;
17use std::sync::LazyLock;
18
19use anyhow::{Context, bail};
20use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
21use risingwave_common::catalog::Schema;
22use risingwave_common::log::LogSuppressor;
23use risingwave_common::row::OwnedRow;
24use risingwave_common::types::{
25 DataType, Date, Datum, Decimal, ScalarImpl, Time, Timestamp, Timestamptz,
26};
27use rust_decimal::Decimal as RustDecimal;
28use thiserror_ext::AsReport;
29use tiberius::Row;
30use tiberius::xml::XmlData;
31use uuid::Uuid;
32
33use crate::parser::utils::log_error;
34
35static LOG_SUPPRESSOR: LazyLock<LogSuppressor> = LazyLock::new(LogSuppressor::default);
36
37pub fn sql_server_row_to_owned_row(row: &mut Row, schema: &Schema) -> OwnedRow {
38 let money_fields = sql_server_money_fields(row);
39 let mut datums = Vec::with_capacity(schema.fields.len());
40 for (i, rw_field) in schema.fields.iter().enumerate() {
41 let name = rw_field.name.as_str();
42 let datum = match sql_server_cell_to_rw_datum(
43 row,
44 i,
45 name,
46 &rw_field.data_type,
47 money_fields.contains(name),
48 ) {
49 Ok(datum) => datum,
50 Err(err) => {
51 log_error!(name, err, "parse column failed");
52 None
53 }
54 };
55 datums.push(datum);
56 }
57 OwnedRow::new(datums)
58}
59
60pub fn sql_server_row_to_owned_row_with_strict_pk(
63 row: &mut Row,
64 schema: &Schema,
65 pk_indices: &[usize],
66) -> anyhow::Result<OwnedRow> {
67 let money_fields = sql_server_money_fields(row);
68 super::decode_row_with_strict_pk(
69 "SQL Server",
70 schema,
71 pk_indices,
72 |index, field| {
73 sql_server_cell_to_rw_datum(
74 row,
75 index,
76 &field.name,
77 &field.data_type,
78 money_fields.contains(&field.name),
79 )
80 },
81 |name, err| log_error!(name, err, "parse column failed"),
82 )
83}
84
85fn sql_server_money_fields(row: &Row) -> HashSet<String> {
86 let mut money_fields = HashSet::new();
87 for (column, _) in row.cells() {
89 if column.column_type() == tiberius::ColumnType::Money {
90 money_fields.insert(column.name().to_owned());
91 }
92 }
93 money_fields
94}
95
96fn sql_server_cell_to_rw_datum(
97 row: &Row,
98 index: usize,
99 name: &str,
100 data_type: &DataType,
101 is_money: bool,
102) -> anyhow::Result<Datum> {
103 if is_money {
104 return row
105 .try_get::<i64, usize>(index)
106 .with_context(|| format!("failed to decode SQL Server money column `{name}`"))?
107 .map(|value| try_convert_money_i64_to_type(value, data_type))
108 .transpose();
109 }
110
111 Ok(row
112 .try_get::<ScalarImplTiberiusWrapper, usize>(index)
113 .with_context(|| format!("failed to decode SQL Server snapshot column `{name}`"))?
114 .map(|datum| datum.0)
115 .map(|scalar| coerce_scalar_to_target_type(scalar, data_type)))
116}
117
118fn coerce_scalar_to_target_type(scalar: ScalarImpl, target_type: &DataType) -> ScalarImpl {
119 match (scalar, target_type) {
120 (ScalarImpl::Int16(v), DataType::Int32) => ScalarImpl::Int32(v as i32),
123 (ScalarImpl::Int16(v), DataType::Int64) => ScalarImpl::Int64(v as i64),
124 (ScalarImpl::Int32(v), DataType::Int64) => ScalarImpl::Int64(v as i64),
125 (ScalarImpl::Float32(v), DataType::Float64) => ScalarImpl::Float64((v.0 as f64).into()),
127 (scalar, _) => scalar,
128 }
129}
130
131fn try_convert_money_i64_to_type(value: i64, data_type: &DataType) -> anyhow::Result<ScalarImpl> {
132 match data_type {
133 DataType::Decimal => Ok(ScalarImpl::Decimal(
134 Decimal::from(value) / Decimal::from_str("10000").unwrap(),
135 )),
136 _ => bail!("conversion of SQL Server money to {data_type} is not supported"),
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use risingwave_common::types::F32;
143
144 use super::*;
145
146 #[test]
147 fn test_integer_upcast_coercion() {
148 let v = coerce_scalar_to_target_type(ScalarImpl::Int32(7), &DataType::Int64);
149 assert_eq!(v, ScalarImpl::Int64(7));
150
151 let v = coerce_scalar_to_target_type(ScalarImpl::Int16(7), &DataType::Int32);
152 assert_eq!(v, ScalarImpl::Int32(7));
153
154 let v = coerce_scalar_to_target_type(ScalarImpl::Int16(7), &DataType::Int64);
155 assert_eq!(v, ScalarImpl::Int64(7));
156 }
157
158 #[test]
159 fn test_float_upcast_coercion() {
160 let v =
161 coerce_scalar_to_target_type(ScalarImpl::Float32(F32::from(1.25)), &DataType::Float64);
162 assert_eq!(v, ScalarImpl::Float64(1.25.into()));
163 }
164
165 #[test]
166 fn test_non_upcast_keeps_original() {
167 let v = coerce_scalar_to_target_type(ScalarImpl::Int32(7), &DataType::Int32);
168 assert_eq!(v, ScalarImpl::Int32(7));
169 }
170}
171macro_rules! impl_tiberius_wrapper {
172 ($wrapper_name:ident, $variant_name:ident) => {
173 pub struct $wrapper_name($variant_name);
174
175 impl From<$variant_name> for $wrapper_name {
176 fn from(value: $variant_name) -> Self {
177 Self(value)
178 }
179 }
180 };
181}
182
183impl_tiberius_wrapper!(ScalarImplTiberiusWrapper, ScalarImpl);
184impl_tiberius_wrapper!(TimeTiberiusWrapper, Time);
185impl_tiberius_wrapper!(DateTiberiusWrapper, Date);
186impl_tiberius_wrapper!(TimestampTiberiusWrapper, Timestamp);
187impl_tiberius_wrapper!(TimestamptzTiberiusWrapper, Timestamptz);
188impl_tiberius_wrapper!(DecimalTiberiusWrapper, Decimal);
189
190macro_rules! impl_chrono_tiberius_wrapper {
191 ($wrapper_name:ident, $variant_name:ident, $chrono:ty) => {
192 impl<'a> tiberius::IntoSql<'a> for $wrapper_name {
193 fn into_sql(self) -> tiberius::ColumnData<'a> {
194 self.0.0.into_sql()
195 }
196 }
197
198 impl<'a> tiberius::FromSql<'a> for $wrapper_name {
199 fn from_sql(
200 value: &'a tiberius::ColumnData<'static>,
201 ) -> tiberius::Result<Option<Self>> {
202 let instant = <$chrono>::from_sql(value)?;
203 let time = instant.map($variant_name::from).map($wrapper_name::from);
204 tiberius::Result::Ok(time)
205 }
206 }
207 };
208}
209
210impl_chrono_tiberius_wrapper!(TimeTiberiusWrapper, Time, NaiveTime);
211impl_chrono_tiberius_wrapper!(DateTiberiusWrapper, Date, NaiveDate);
212impl_chrono_tiberius_wrapper!(TimestampTiberiusWrapper, Timestamp, NaiveDateTime);
213
214impl<'a> tiberius::IntoSql<'a> for DecimalTiberiusWrapper {
215 fn into_sql(self) -> tiberius::ColumnData<'a> {
216 match self.0 {
217 Decimal::Normalized(d) => d.into_sql(),
218 Decimal::NaN => tiberius::ColumnData::Numeric(None),
219 Decimal::PositiveInf => tiberius::ColumnData::Numeric(None),
220 Decimal::NegativeInf => tiberius::ColumnData::Numeric(None),
221 }
222 }
223}
224
225impl<'a> tiberius::FromSql<'a> for DecimalTiberiusWrapper {
226 fn from_sql(value: &'a tiberius::ColumnData<'static>) -> tiberius::Result<Option<Self>> {
228 tiberius::Result::Ok(
229 RustDecimal::from_sql(value)?
230 .map(Decimal::Normalized)
231 .map(DecimalTiberiusWrapper::from),
232 )
233 }
234}
235
236impl<'a> tiberius::IntoSql<'a> for TimestamptzTiberiusWrapper {
237 fn into_sql(self) -> tiberius::ColumnData<'a> {
238 self.0.to_datetime_utc().into_sql()
239 }
240}
241
242impl<'a> tiberius::FromSql<'a> for TimestamptzTiberiusWrapper {
243 fn from_sql(value: &'a tiberius::ColumnData<'static>) -> tiberius::Result<Option<Self>> {
244 let instant = time::OffsetDateTime::from_sql(value)?;
245 instant
246 .map(|instant| {
247 let timestamptz = instant
248 .unix_timestamp_nanos()
249 .checked_div(1000)
250 .and_then(|micros| i64::try_from(micros).ok())
251 .and_then(Timestamptz::from_micros)
252 .ok_or_else(|| {
253 tiberius::error::Error::Conversion(
254 "datetimeoffset is out of range for RisingWave timestamptz".into(),
255 )
256 })?;
257 Ok(TimestamptzTiberiusWrapper::from(timestamptz))
258 })
259 .transpose()
260 }
261}
262
263impl<'a> tiberius::FromSql<'a> for ScalarImplTiberiusWrapper {
297 fn from_sql(value: &'a tiberius::ColumnData<'static>) -> tiberius::Result<Option<Self>> {
298 Ok(match &value {
299 tiberius::ColumnData::U8(_) => u8::from_sql(value)?
300 .map(|v| ScalarImplTiberiusWrapper::from(ScalarImpl::from(v as i16))),
301 tiberius::ColumnData::I16(_) => i16::from_sql(value)?
302 .map(ScalarImpl::from)
303 .map(ScalarImplTiberiusWrapper::from),
304 tiberius::ColumnData::I32(_) => i32::from_sql(value)?
305 .map(ScalarImpl::from)
306 .map(ScalarImplTiberiusWrapper::from),
307 tiberius::ColumnData::I64(_) => i64::from_sql(value)?
308 .map(ScalarImpl::from)
309 .map(ScalarImplTiberiusWrapper::from),
310 tiberius::ColumnData::F32(_) => f32::from_sql(value)?
311 .map(ScalarImpl::from)
312 .map(ScalarImplTiberiusWrapper::from),
313 tiberius::ColumnData::F64(_) => f64::from_sql(value)?
314 .map(ScalarImpl::from)
315 .map(ScalarImplTiberiusWrapper::from),
316 tiberius::ColumnData::Bit(_) => bool::from_sql(value)?
317 .map(ScalarImpl::from)
318 .map(ScalarImplTiberiusWrapper::from),
319 tiberius::ColumnData::String(_) => <&str>::from_sql(value)?
320 .map(ScalarImpl::from)
321 .map(ScalarImplTiberiusWrapper::from),
322 tiberius::ColumnData::Numeric(_) => DecimalTiberiusWrapper::from_sql(value)?
323 .map(|w| ScalarImpl::from(w.0))
324 .map(ScalarImplTiberiusWrapper::from),
325 tiberius::ColumnData::DateTime(_)
326 | tiberius::ColumnData::DateTime2(_)
327 | tiberius::ColumnData::SmallDateTime(_) => TimestampTiberiusWrapper::from_sql(value)?
328 .map(|w| ScalarImpl::from(w.0))
329 .map(ScalarImplTiberiusWrapper::from),
330 tiberius::ColumnData::Time(_) => TimeTiberiusWrapper::from_sql(value)?
331 .map(|w| ScalarImpl::from(w.0))
332 .map(ScalarImplTiberiusWrapper::from),
333 tiberius::ColumnData::Date(_) => DateTiberiusWrapper::from_sql(value)?
334 .map(|w| ScalarImpl::from(w.0))
335 .map(ScalarImplTiberiusWrapper::from),
336 tiberius::ColumnData::DateTimeOffset(_) => TimestamptzTiberiusWrapper::from_sql(value)?
337 .map(|w| ScalarImpl::from(w.0))
338 .map(ScalarImplTiberiusWrapper::from),
339 tiberius::ColumnData::Binary(_) => <&[u8]>::from_sql(value)?
340 .map(ScalarImpl::from)
341 .map(ScalarImplTiberiusWrapper::from),
342 tiberius::ColumnData::Guid(_) => <Uuid>::from_sql(value)?
343 .map(|uuid| uuid.to_string().to_uppercase())
344 .map(ScalarImpl::from)
345 .map(ScalarImplTiberiusWrapper::from),
346 tiberius::ColumnData::Xml(_) => <&XmlData>::from_sql(value)?
347 .map(|xml| xml.clone().into_string())
348 .map(ScalarImpl::from)
349 .map(ScalarImplTiberiusWrapper::from),
350 })
351 }
352}
353
354impl<'a> tiberius::IntoSql<'a> for ScalarImplTiberiusWrapper {
402 fn into_sql(self) -> tiberius::ColumnData<'a> {
403 match self.0 {
404 ScalarImpl::Int16(v) => v.into_sql(),
405 ScalarImpl::Int32(v) => v.into_sql(),
406 ScalarImpl::Int64(v) => v.into_sql(),
407 ScalarImpl::Float32(v) => v.0.into_sql(),
408 ScalarImpl::Float64(v) => v.0.into_sql(),
409 ScalarImpl::Bool(v) => v.into_sql(),
410 ScalarImpl::Decimal(v) => DecimalTiberiusWrapper::from(v).into_sql(),
411 ScalarImpl::Date(v) => DateTiberiusWrapper::from(v).into_sql(),
412 ScalarImpl::Timestamp(v) => TimestampTiberiusWrapper::from(v).into_sql(),
413 ScalarImpl::Timestamptz(v) => TimestamptzTiberiusWrapper::from(v).into_sql(),
414 ScalarImpl::Time(v) => TimeTiberiusWrapper::from(v).into_sql(),
415 ScalarImpl::Bytea(v) => {
416 let value: Vec<u8> = (*v).to_vec();
417 value.into_sql()
418 }
419 ScalarImpl::Utf8(v) => String::from(v).into_sql(),
420 value => {
421 unimplemented!("the sql server decoding for {:?} is unsupported", value);
423 }
424 }
425 }
426}