Skip to content

Commit 8ddcc7d

Browse files
committed
fix(odbc) fix date and time support
1 parent 238c76d commit 8ddcc7d

File tree

7 files changed

+228
-71
lines changed

7 files changed

+228
-71
lines changed

sqlx-core/src/odbc/arguments.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ pub struct OdbcArguments {
88
pub(crate) values: Vec<OdbcArgumentValue>,
99
}
1010

11-
#[derive(Debug, Clone)]
11+
#[derive(Debug, Clone, PartialEq)]
1212
pub enum OdbcArgumentValue {
1313
Text(String),
1414
Bytes(Vec<u8>),
1515
Int(i64),
1616
Float(f64),
17+
Date(odbc_api::sys::Date),
18+
Time(odbc_api::sys::Time),
19+
Timestamp(odbc_api::sys::Timestamp),
1720
Null,
1821
}
1922

sqlx-core/src/odbc/connection/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::odbc::{
66
};
77
use crate::transaction::Transaction;
88
use either::Either;
9+
use odbc_api::handles::AsStatementRef;
910
use sqlx_rt::spawn_blocking;
1011
mod odbc_bridge;
1112
use crate::odbc::{OdbcStatement, OdbcStatementMetadata};
@@ -210,6 +211,18 @@ pub(crate) enum MaybePrepared {
210211
NotPrepared(String),
211212
}
212213

214+
impl std::fmt::Debug for MaybePrepared {
215+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216+
match self {
217+
MaybePrepared::Prepared(prepared) => f
218+
.debug_tuple("Prepared")
219+
.field(&prepared.lock().unwrap().as_stmt_ref())
220+
.finish(),
221+
MaybePrepared::NotPrepared(sql) => f.debug_tuple("NotPrepared").field(sql).finish(),
222+
}
223+
}
224+
}
225+
213226
impl Connection for OdbcConnection {
214227
type Database = Odbc;
215228

sqlx-core/src/odbc/connection/odbc_bridge.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub fn execute_sql(
6969
tx: &ExecuteSender,
7070
buffer_settings: OdbcBufferSettings,
7171
) -> Result<(), Error> {
72+
log::trace!("Executing {:?} with params {:?}", maybe_prepared, args);
7273
let params = prepare_parameters(args);
7374

7475
let affected = match maybe_prepared {
@@ -119,11 +120,35 @@ fn prepare_parameters(
119120
}
120121

121122
fn to_param(arg: OdbcArgumentValue) -> Box<dyn odbc_api::parameter::InputParameter + 'static> {
123+
use odbc_api::parameter::WithDataType;
124+
use odbc_api::DataType;
125+
122126
match arg {
123127
OdbcArgumentValue::Int(i) => Box::new(i.into_parameter()),
124128
OdbcArgumentValue::Float(f) => Box::new(f.into_parameter()),
125129
OdbcArgumentValue::Text(s) => Box::new(s.into_parameter()),
126130
OdbcArgumentValue::Bytes(b) => Box::new(b.into_parameter()),
131+
OdbcArgumentValue::Date(d) => Box::new(
132+
WithDataType {
133+
value: d,
134+
data_type: DataType::Date,
135+
}
136+
.into_parameter(),
137+
),
138+
OdbcArgumentValue::Time(t) => Box::new(
139+
WithDataType {
140+
value: t,
141+
data_type: DataType::Time { precision: 0 },
142+
}
143+
.into_parameter(),
144+
),
145+
OdbcArgumentValue::Timestamp(ts) => Box::new(
146+
WithDataType {
147+
value: ts,
148+
data_type: DataType::Timestamp { precision: 6 },
149+
}
150+
.into_parameter(),
151+
),
127152
OdbcArgumentValue::Null => Box::new(Option::<String>::None.into_parameter()),
128153
}
129154
}

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

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use crate::error::BoxDynError;
44
use crate::odbc::{DataTypeExt, Odbc, OdbcArgumentValue, OdbcTypeInfo, OdbcValueRef};
55
use crate::type_info::TypeInfo;
66
use crate::types::Type;
7-
use chrono::{DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc};
7+
use chrono::{
8+
DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc,
9+
};
810
use odbc_api::DataType;
911

1012
impl Type<Odbc> for NaiveDate {
@@ -80,88 +82,112 @@ impl Type<Odbc> for DateTime<Local> {
8082

8183
impl<'q> Encode<'q, Odbc> for NaiveDate {
8284
fn encode(self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
83-
buf.push(OdbcArgumentValue::Text(self.format("%Y-%m-%d").to_string()));
85+
buf.push(OdbcArgumentValue::Date(odbc_api::sys::Date {
86+
year: self.year() as i16,
87+
month: self.month() as u16,
88+
day: self.day() as u16,
89+
}));
8490
crate::encode::IsNull::No
8591
}
8692

8793
fn encode_by_ref(&self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
88-
buf.push(OdbcArgumentValue::Text(self.format("%Y-%m-%d").to_string()));
94+
buf.push(OdbcArgumentValue::Date(odbc_api::sys::Date {
95+
year: self.year() as i16,
96+
month: self.month() as u16,
97+
day: self.day() as u16,
98+
}));
8999
crate::encode::IsNull::No
90100
}
91101
}
92102

93103
impl<'q> Encode<'q, Odbc> for NaiveTime {
94104
fn encode(self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
95-
buf.push(OdbcArgumentValue::Text(self.format("%H:%M:%S").to_string()));
105+
buf.push(OdbcArgumentValue::Time(odbc_api::sys::Time {
106+
hour: self.hour() as u16,
107+
minute: self.minute() as u16,
108+
second: self.second() as u16,
109+
}));
96110
crate::encode::IsNull::No
97111
}
98112

99113
fn encode_by_ref(&self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
100-
buf.push(OdbcArgumentValue::Text(self.format("%H:%M:%S").to_string()));
114+
buf.push(OdbcArgumentValue::Time(odbc_api::sys::Time {
115+
hour: self.hour() as u16,
116+
minute: self.minute() as u16,
117+
second: self.second() as u16,
118+
}));
101119
crate::encode::IsNull::No
102120
}
103121
}
104122

105123
impl<'q> Encode<'q, Odbc> for NaiveDateTime {
106124
fn encode(self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
107-
buf.push(OdbcArgumentValue::Text(
108-
self.format("%Y-%m-%d %H:%M:%S").to_string(),
109-
));
125+
buf.push(OdbcArgumentValue::Timestamp(odbc_api::sys::Timestamp {
126+
year: self.year() as i16,
127+
month: self.month() as u16,
128+
day: self.day() as u16,
129+
hour: self.hour() as u16,
130+
minute: self.minute() as u16,
131+
second: self.second() as u16,
132+
fraction: self.nanosecond(),
133+
}));
110134
crate::encode::IsNull::No
111135
}
112136

113137
fn encode_by_ref(&self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
114-
buf.push(OdbcArgumentValue::Text(
115-
self.format("%Y-%m-%d %H:%M:%S").to_string(),
116-
));
138+
buf.push(OdbcArgumentValue::Timestamp(odbc_api::sys::Timestamp {
139+
year: self.year() as i16,
140+
month: self.month() as u16,
141+
day: self.day() as u16,
142+
hour: self.hour() as u16,
143+
minute: self.minute() as u16,
144+
second: self.second() as u16,
145+
fraction: self.nanosecond(),
146+
}));
117147
crate::encode::IsNull::No
118148
}
119149
}
120150

121151
impl<'q> Encode<'q, Odbc> for DateTime<Utc> {
122-
fn encode(self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
123-
buf.push(OdbcArgumentValue::Text(
124-
self.format("%Y-%m-%d %H:%M:%S").to_string(),
125-
));
126-
crate::encode::IsNull::No
127-
}
128-
129152
fn encode_by_ref(&self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
130-
buf.push(OdbcArgumentValue::Text(
131-
self.format("%Y-%m-%d %H:%M:%S").to_string(),
132-
));
153+
buf.push(OdbcArgumentValue::Text(self.to_rfc3339()));
133154
crate::encode::IsNull::No
134155
}
135156
}
136157

137158
impl<'q> Encode<'q, Odbc> for DateTime<FixedOffset> {
138-
fn encode(self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
139-
buf.push(OdbcArgumentValue::Text(
140-
self.format("%Y-%m-%d %H:%M:%S").to_string(),
141-
));
142-
crate::encode::IsNull::No
143-
}
144-
145159
fn encode_by_ref(&self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
146-
buf.push(OdbcArgumentValue::Text(
147-
self.format("%Y-%m-%d %H:%M:%S").to_string(),
148-
));
160+
buf.push(OdbcArgumentValue::Text(self.to_rfc3339()));
149161
crate::encode::IsNull::No
150162
}
151163
}
152164

153165
impl<'q> Encode<'q, Odbc> for DateTime<Local> {
154166
fn encode(self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
155-
buf.push(OdbcArgumentValue::Text(
156-
self.format("%Y-%m-%d %H:%M:%S").to_string(),
157-
));
167+
let naive = self.naive_local();
168+
buf.push(OdbcArgumentValue::Timestamp(odbc_api::sys::Timestamp {
169+
year: naive.year() as i16,
170+
month: naive.month() as u16,
171+
day: naive.day() as u16,
172+
hour: naive.hour() as u16,
173+
minute: naive.minute() as u16,
174+
second: naive.second() as u16,
175+
fraction: naive.nanosecond(),
176+
}));
158177
crate::encode::IsNull::No
159178
}
160179

161180
fn encode_by_ref(&self, buf: &mut Vec<OdbcArgumentValue>) -> crate::encode::IsNull {
162-
buf.push(OdbcArgumentValue::Text(
163-
self.format("%Y-%m-%d %H:%M:%S").to_string(),
164-
));
181+
let naive = self.naive_local();
182+
buf.push(OdbcArgumentValue::Timestamp(odbc_api::sys::Timestamp {
183+
year: naive.year() as i16,
184+
month: naive.month() as u16,
185+
day: naive.day() as u16,
186+
hour: naive.hour() as u16,
187+
minute: naive.minute() as u16,
188+
second: naive.second() as u16,
189+
fraction: naive.nanosecond(),
190+
}));
165191
crate::encode::IsNull::No
166192
}
167193
}
@@ -621,11 +647,14 @@ mod tests {
621647
let result = <NaiveDate as Encode<Odbc>>::encode(date, &mut buf);
622648
assert!(matches!(result, crate::encode::IsNull::No));
623649
assert_eq!(buf.len(), 1);
624-
if let OdbcArgumentValue::Text(text) = &buf[0] {
625-
assert_eq!(text, "2020-01-02");
626-
} else {
627-
panic!("Expected Text argument");
628-
}
650+
assert_eq!(
651+
buf[0],
652+
OdbcArgumentValue::Date(odbc_api::sys::Date {
653+
year: 2020,
654+
month: 1,
655+
day: 2,
656+
})
657+
);
629658
}
630659

631660
#[test]

sqlx-test/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ macro_rules! test_unprepared_type {
105105
let row = s.try_next().await?.unwrap();
106106
let rec = row.try_get::<$ty, _>(0)?;
107107

108-
assert_eq!($value, rec);
108+
assert_eq!($value, rec, "Value mismatch on {row:?}: Expected {:#?} but received {rec:#?} ({}) from db", $value, std::any::type_name::<$ty>());
109109

110110
drop(s);
111111
)+
@@ -166,24 +166,26 @@ macro_rules! __test_prepared_type {
166166
.fetch_one(&mut conn)
167167
.await?;
168168

169+
println!("Row: {:?}", row);
170+
169171
let matches: i32 = row.try_get(0)?;
170172
let returned: $ty = row.try_get(1)?;
171173
let round_trip: $ty = row.try_get(2)?;
172174

173175
assert!(matches != 0,
174-
"[1] DB value mismatch; given value: {:?}\n\
176+
"[1] DB value mismatch (SQL query returned non-zero value); given value: {:?}\n\
175177
as returned: {:?}\n\
176178
round-trip: {:?}",
177179
$value, returned, round_trip);
178180

179181
assert_eq!($value, returned,
180-
"[2] DB value mismatch; given value: {:?}\n\
182+
"[2] DB value mismatch (given value does not match returned); given value: {:?}\n\
181183
as returned: {:?}\n\
182184
round-trip: {:?}",
183185
$value, returned, round_trip);
184186

185187
assert_eq!($value, round_trip,
186-
"[3] DB value mismatch; given value: {:?}\n\
188+
"[3] DB value mismatch (given value does not match round-trip); given value: {:?}\n\
187189
as returned: {:?}\n\
188190
round-trip: {:?}",
189191
$value, returned, round_trip);

0 commit comments

Comments
 (0)