Skip to content

Commit 0ff2953

Browse files
committed
feat(odbc): add support for raw ODBC Date, Time, and Timestamp values
This commit introduces methods to retrieve raw ODBC Date, Time, and Timestamp values in the OdbcValueRef structure. Additionally, the Decode implementations for NaiveDate, NaiveTime, NaiveDateTime, and DateTime types are updated to prioritize these raw values, enhancing the handling of date and time data in the ODBC bridge.
1 parent c4b1b8f commit 0ff2953

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

sqlx-core/src/odbc/types/chrono.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,17 @@ fn get_text_from_value(value: &OdbcValueRef<'_>) -> Result<Option<String>, BoxDy
206206

207207
impl<'r> Decode<'r, Odbc> for NaiveDate {
208208
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
209+
// Handle raw ODBC Date values first
210+
if let Some(date_val) = value.date() {
211+
// Convert odbc_api::sys::Date to NaiveDate
212+
// The ODBC Date structure typically has year, month, day fields
213+
return Ok(NaiveDate::from_ymd_opt(
214+
date_val.year as i32,
215+
date_val.month as u32,
216+
date_val.day as u32,
217+
).ok_or_else(|| "ODBC: invalid date values".to_string())?);
218+
}
219+
209220
// Handle text values first (most common for dates)
210221
if let Some(text) = value.text() {
211222
if let Some(date) = parse_yyyymmdd_text_as_naive_date(text) {
@@ -250,6 +261,17 @@ impl<'r> Decode<'r, Odbc> for NaiveDate {
250261

251262
impl<'r> Decode<'r, Odbc> for NaiveTime {
252263
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
264+
// Handle raw ODBC Time values first
265+
if let Some(time_val) = value.time() {
266+
// Convert odbc_api::sys::Time to NaiveTime
267+
// The ODBC Time structure typically has hour, minute, second fields
268+
return Ok(NaiveTime::from_hms_opt(
269+
time_val.hour as u32,
270+
time_val.minute as u32,
271+
time_val.second as u32,
272+
).ok_or_else(|| "ODBC: invalid time values".to_string())?);
273+
}
274+
253275
let mut s = <String as Decode<'r, Odbc>>::decode(value)?;
254276
if s.ends_with('\u{0}') {
255277
s = s.trim_end_matches('\u{0}').to_string();
@@ -263,6 +285,25 @@ impl<'r> Decode<'r, Odbc> for NaiveTime {
263285

264286
impl<'r> Decode<'r, Odbc> for NaiveDateTime {
265287
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
288+
// Handle raw ODBC Timestamp values first
289+
if let Some(ts_val) = value.timestamp() {
290+
// Convert odbc_api::sys::Timestamp to NaiveDateTime
291+
// The ODBC Timestamp structure typically has year, month, day, hour, minute, second fields
292+
let date = NaiveDate::from_ymd_opt(
293+
ts_val.year as i32,
294+
ts_val.month as u32,
295+
ts_val.day as u32,
296+
).ok_or_else(|| "ODBC: invalid date values in timestamp".to_string())?;
297+
298+
let time = NaiveTime::from_hms_opt(
299+
ts_val.hour as u32,
300+
ts_val.minute as u32,
301+
ts_val.second as u32,
302+
).ok_or_else(|| "ODBC: invalid time values in timestamp".to_string())?;
303+
304+
return Ok(NaiveDateTime::new(date, time));
305+
}
306+
266307
let mut s = <String as Decode<'r, Odbc>>::decode(value)?;
267308
// Some ODBC drivers (e.g. PostgreSQL) may include trailing spaces or NULs
268309
// in textual representations of timestamps. Trim them before parsing.
@@ -285,6 +326,26 @@ impl<'r> Decode<'r, Odbc> for NaiveDateTime {
285326

286327
impl<'r> Decode<'r, Odbc> for DateTime<Utc> {
287328
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
329+
// Handle raw ODBC Timestamp values first
330+
if let Some(ts_val) = value.timestamp() {
331+
// Convert odbc_api::sys::Timestamp to DateTime<Utc>
332+
// The ODBC Timestamp structure typically has year, month, day, hour, minute, second fields
333+
let naive_dt = NaiveDateTime::new(
334+
NaiveDate::from_ymd_opt(
335+
ts_val.year as i32,
336+
ts_val.month as u32,
337+
ts_val.day as u32,
338+
).ok_or_else(|| "ODBC: invalid date values in timestamp".to_string())?,
339+
NaiveTime::from_hms_opt(
340+
ts_val.hour as u32,
341+
ts_val.minute as u32,
342+
ts_val.second as u32,
343+
).ok_or_else(|| "ODBC: invalid time values in timestamp".to_string())?,
344+
);
345+
346+
return Ok(DateTime::<Utc>::from_naive_utc_and_offset(naive_dt, Utc));
347+
}
348+
288349
let mut s = <String as Decode<'r, Odbc>>::decode(value)?;
289350
if s.ends_with('\u{0}') {
290351
s = s.trim_end_matches('\u{0}').to_string();
@@ -312,6 +373,26 @@ impl<'r> Decode<'r, Odbc> for DateTime<Utc> {
312373

313374
impl<'r> Decode<'r, Odbc> for DateTime<FixedOffset> {
314375
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
376+
// Handle raw ODBC Timestamp values first
377+
if let Some(ts_val) = value.timestamp() {
378+
// Convert odbc_api::sys::Timestamp to DateTime<FixedOffset>
379+
// The ODBC Timestamp structure typically has year, month, day, hour, minute, second fields
380+
let naive_dt = NaiveDateTime::new(
381+
NaiveDate::from_ymd_opt(
382+
ts_val.year as i32,
383+
ts_val.month as u32,
384+
ts_val.day as u32,
385+
).ok_or_else(|| "ODBC: invalid date values in timestamp".to_string())?,
386+
NaiveTime::from_hms_opt(
387+
ts_val.hour as u32,
388+
ts_val.minute as u32,
389+
ts_val.second as u32,
390+
).ok_or_else(|| "ODBC: invalid time values in timestamp".to_string())?,
391+
);
392+
393+
return Ok(DateTime::<Utc>::from_naive_utc_and_offset(naive_dt, Utc).fixed_offset());
394+
}
395+
315396
let mut s = <String as Decode<'r, Odbc>>::decode(value)?;
316397
if s.ends_with('\u{0}') {
317398
s = s.trim_end_matches('\u{0}').to_string();
@@ -343,6 +424,26 @@ impl<'r> Decode<'r, Odbc> for DateTime<FixedOffset> {
343424

344425
impl<'r> Decode<'r, Odbc> for DateTime<Local> {
345426
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
427+
// Handle raw ODBC Timestamp values first
428+
if let Some(ts_val) = value.timestamp() {
429+
// Convert odbc_api::sys::Timestamp to DateTime<Local>
430+
// The ODBC Timestamp structure typically has year, month, day, hour, minute, second fields
431+
let naive_dt = NaiveDateTime::new(
432+
NaiveDate::from_ymd_opt(
433+
ts_val.year as i32,
434+
ts_val.month as u32,
435+
ts_val.day as u32,
436+
).ok_or_else(|| "ODBC: invalid date values in timestamp".to_string())?,
437+
NaiveTime::from_hms_opt(
438+
ts_val.hour as u32,
439+
ts_val.minute as u32,
440+
ts_val.second as u32,
441+
).ok_or_else(|| "ODBC: invalid time values in timestamp".to_string())?,
442+
);
443+
444+
return Ok(DateTime::<Utc>::from_naive_utc_and_offset(naive_dt, Utc).with_timezone(&Local));
445+
}
446+
346447
let mut s = <String as Decode<'r, Odbc>>::decode(value)?;
347448
if s.ends_with('\u{0}') {
348449
s = s.trim_end_matches('\u{0}').to_string();

sqlx-core/src/odbc/types/time.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,15 @@ fn parse_yyyymmdd_text_as_time_date(s: &str) -> Option<Date> {
227227

228228
impl<'r> Decode<'r, Odbc> for Date {
229229
fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
230+
// Handle raw ODBC Date values first
231+
if let Some(date_val) = value.date() {
232+
// Convert odbc_api::sys::Date to time::Date
233+
// The ODBC Date structure typically has year, month, day fields
234+
let month = time::Month::try_from(date_val.month as u8)
235+
.map_err(|_| "ODBC: invalid month value")?;
236+
return Ok(Date::from_calendar_date(date_val.year as i32, month, date_val.day as u8)?);
237+
}
238+
230239
// Handle numeric YYYYMMDD format first
231240
if let Some(int_val) = value.int() {
232241
if let Some(date) = parse_yyyymmdd_as_time_date(int_val) {

sqlx-core/src/odbc/value.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,48 @@ impl<'r> OdbcValueRef<'r> {
208208
pub fn blob(&self) -> Option<&'r [u8]> {
209209
value_vec_blob(&self.column_data.values, self.row_index)
210210
}
211+
212+
/// Try to get the raw ODBC Date value
213+
pub fn date(&self) -> Option<odbc_api::sys::Date> {
214+
match &self.column_data.values {
215+
OdbcValueVec::Date { raw_values, nulls } => {
216+
if nulls.get(self.row_index).copied().unwrap_or(false) {
217+
None
218+
} else {
219+
raw_values.get(self.row_index).copied()
220+
}
221+
}
222+
_ => None,
223+
}
224+
}
225+
226+
/// Try to get the raw ODBC Time value
227+
pub fn time(&self) -> Option<odbc_api::sys::Time> {
228+
match &self.column_data.values {
229+
OdbcValueVec::Time { raw_values, nulls } => {
230+
if nulls.get(self.row_index).copied().unwrap_or(false) {
231+
None
232+
} else {
233+
raw_values.get(self.row_index).copied()
234+
}
235+
}
236+
_ => None,
237+
}
238+
}
239+
240+
/// Try to get the raw ODBC Timestamp value
241+
pub fn timestamp(&self) -> Option<odbc_api::sys::Timestamp> {
242+
match &self.column_data.values {
243+
OdbcValueVec::Timestamp { raw_values, nulls } => {
244+
if nulls.get(self.row_index).copied().unwrap_or(false) {
245+
None
246+
} else {
247+
raw_values.get(self.row_index).copied()
248+
}
249+
}
250+
_ => None,
251+
}
252+
}
211253
}
212254

213255
/// Individual ODBC value type

0 commit comments

Comments
 (0)