sqlx_exasol/types/
chrono.rs1use std::{
2 borrow::Cow,
3 ops::{Add, Sub},
4};
5
6use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeZone, Utc};
7use serde::Deserialize;
8use sqlx_core::{
9 decode::Decode,
10 encode::{Encode, IsNull},
11 error::BoxDynError,
12 types::Type,
13};
14
15use crate::{
16 arguments::ExaBuffer,
17 database::Exasol,
18 type_info::{ExaDataType, ExaTypeInfo, IntervalDayToSecond, IntervalYearToMonth},
19 value::ExaValueRef,
20};
21
22const TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S%.6f";
23
24impl Type<Exasol> for DateTime<Utc> {
25 fn type_info() -> ExaTypeInfo {
26 ExaDataType::Timestamp.into()
27 }
28}
29
30impl Encode<'_, Exasol> for DateTime<Utc> {
31 fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result<IsNull, BoxDynError> {
32 Encode::<Exasol>::encode(self.naive_utc(), buf)
33 }
34
35 fn produces(&self) -> Option<ExaTypeInfo> {
36 Some(ExaDataType::Timestamp.into())
37 }
38
39 fn size_hint(&self) -> usize {
40 TIMESTAMP_FMT.len()
41 }
42}
43
44impl<'r> Decode<'r, Exasol> for DateTime<Utc> {
45 fn decode(value: ExaValueRef<'r>) -> Result<Self, BoxDynError> {
46 let naive: NaiveDateTime = Decode::<Exasol>::decode(value)?;
47 Ok(DateTime::from_naive_utc_and_offset(naive, Utc))
48 }
49}
50
51impl Type<Exasol> for DateTime<Local> {
52 fn type_info() -> ExaTypeInfo {
53 ExaDataType::TimestampWithLocalTimeZone.into()
54 }
55}
56
57impl Encode<'_, Exasol> for DateTime<Local> {
58 fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result<IsNull, BoxDynError> {
59 Encode::<Exasol>::encode(self.naive_local(), buf)
60 }
61
62 fn produces(&self) -> Option<ExaTypeInfo> {
63 Some(ExaDataType::TimestampWithLocalTimeZone.into())
64 }
65
66 fn size_hint(&self) -> usize {
67 TIMESTAMP_FMT.len()
68 }
69}
70
71impl<'r> Decode<'r, Exasol> for DateTime<Local> {
72 fn decode(value: ExaValueRef<'r>) -> Result<Self, BoxDynError> {
73 let naive: NaiveDateTime = Decode::<Exasol>::decode(value)?;
74 naive
75 .and_local_timezone(Local)
76 .single()
77 .ok_or("cannot uniquely determine timezone offset")
78 .map_err(From::from)
79 }
80}
81
82impl Type<Exasol> for NaiveDateTime {
83 fn type_info() -> ExaTypeInfo {
84 ExaDataType::Timestamp.into()
85 }
86}
87
88impl Encode<'_, Exasol> for NaiveDateTime {
89 fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result<IsNull, BoxDynError> {
90 buf.append(format_args!("{}", self.format(TIMESTAMP_FMT)))?;
91 Ok(IsNull::No)
92 }
93
94 fn produces(&self) -> Option<ExaTypeInfo> {
95 Some(ExaDataType::Timestamp.into())
96 }
97
98 fn size_hint(&self) -> usize {
99 TIMESTAMP_FMT.len()
100 }
101}
102
103impl Decode<'_, Exasol> for NaiveDateTime {
104 fn decode(value: ExaValueRef<'_>) -> Result<Self, BoxDynError> {
105 let input = Cow::<str>::deserialize(value.value).map_err(Box::new)?;
106 Self::parse_from_str(&input, TIMESTAMP_FMT)
107 .map_err(Box::new)
108 .map_err(From::from)
109 }
110}
111
112impl Type<Exasol> for chrono::Duration {
113 fn type_info() -> ExaTypeInfo {
114 let ids = IntervalDayToSecond::new(
115 IntervalDayToSecond::MAX_PRECISION,
116 IntervalDayToSecond::MAX_SUPPORTED_FRACTION,
117 );
118 ExaDataType::IntervalDayToSecond(ids).into()
119 }
120
121 fn compatible(ty: &ExaTypeInfo) -> bool {
122 <Self as Type<Exasol>>::type_info().compatible(ty)
123 }
124}
125
126impl Encode<'_, Exasol> for chrono::Duration {
127 fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result<IsNull, BoxDynError> {
128 buf.append(format_args!(
129 "{} {}:{}:{}.{}",
130 self.num_days(),
131 self.num_hours().abs() % 24,
132 self.num_minutes().abs() % 60,
133 self.num_seconds().abs() % 60,
134 self.num_milliseconds().abs() % 1000
135 ))?;
136
137 Ok(IsNull::No)
138 }
139
140 fn produces(&self) -> Option<ExaTypeInfo> {
141 let precision = self
142 .num_days()
143 .unsigned_abs()
144 .checked_ilog10()
145 .unwrap_or_default()
146 + 1;
147
148 let fraction = (self.num_milliseconds() % 1000)
149 .unsigned_abs()
150 .checked_ilog10()
151 .map(|v| v + 1)
152 .unwrap_or_default();
153
154 Some(ExaDataType::IntervalDayToSecond(IntervalDayToSecond::new(precision, fraction)).into())
155 }
156
157 fn size_hint(&self) -> usize {
158 1 + IntervalDayToSecond::MAX_PRECISION as usize
161 + 10
162 + IntervalDayToSecond::MAX_SUPPORTED_FRACTION as usize
163 }
164}
165
166impl<'r> Decode<'r, Exasol> for chrono::Duration {
167 fn decode(value: ExaValueRef<'r>) -> Result<Self, BoxDynError> {
168 let input = Cow::<str>::deserialize(value.value).map_err(Box::new)?;
169 let input_err_fn = || format!("could not parse {input} as INTERVAL DAY TO SECOND");
170
171 let (days, rest) = input.split_once(' ').ok_or_else(input_err_fn)?;
172 let (hours, rest) = rest.split_once(':').ok_or_else(input_err_fn)?;
173 let (minutes, rest) = rest.split_once(':').ok_or_else(input_err_fn)?;
174 let (seconds, millis) = rest.split_once('.').ok_or_else(input_err_fn)?;
175
176 let days: i64 = days.parse().map_err(Box::new)?;
177 let hours: i64 = hours.parse().map_err(Box::new)?;
178 let minutes: i64 = minutes.parse().map_err(Box::new)?;
179 let seconds: i64 = seconds.parse().map_err(Box::new)?;
180 let millis: i64 = millis.parse().map_err(Box::new)?;
181 let sign = if days.is_negative() { -1 } else { 1 };
182
183 let duration = chrono::Duration::try_days(days).ok_or_else(input_err_fn)?
184 + chrono::Duration::try_hours(hours * sign).ok_or_else(input_err_fn)?
185 + chrono::Duration::try_minutes(minutes * sign).ok_or_else(input_err_fn)?
186 + chrono::Duration::try_seconds(seconds * sign).ok_or_else(input_err_fn)?
187 + chrono::Duration::try_milliseconds(millis * sign).ok_or_else(input_err_fn)?;
188
189 Ok(duration)
190 }
191}
192
193impl Type<Exasol> for NaiveDate {
194 fn type_info() -> ExaTypeInfo {
195 ExaDataType::Date.into()
196 }
197}
198
199impl Encode<'_, Exasol> for NaiveDate {
200 fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result<IsNull, BoxDynError> {
201 buf.append(self)?;
202 Ok(IsNull::No)
203 }
204
205 fn produces(&self) -> Option<ExaTypeInfo> {
206 Some(ExaDataType::Date.into())
207 }
208
209 fn size_hint(&self) -> usize {
210 10
212 }
213}
214
215impl Decode<'_, Exasol> for NaiveDate {
216 fn decode(value: ExaValueRef<'_>) -> Result<Self, BoxDynError> {
217 <Self as Deserialize>::deserialize(value.value).map_err(From::from)
218 }
219}
220
221#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
224pub struct Months(i32);
225
226impl Months {
227 #[must_use]
228 pub const fn new(num: i32) -> Self {
229 Self(num)
230 }
231
232 #[must_use]
233 pub fn num_months(&self) -> i32 {
234 self.0
235 }
236}
237
238impl Type<Exasol> for Months {
239 fn type_info() -> ExaTypeInfo {
240 let iym = IntervalYearToMonth::new(IntervalYearToMonth::MAX_PRECISION);
241 ExaDataType::IntervalYearToMonth(iym).into()
242 }
243
244 fn compatible(ty: &ExaTypeInfo) -> bool {
245 <Self as Type<Exasol>>::type_info().compatible(ty)
246 }
247}
248
249impl Encode<'_, Exasol> for Months {
250 fn encode_by_ref(&self, buf: &mut ExaBuffer) -> Result<IsNull, BoxDynError> {
251 let years = self.0 / 12;
252 let months = (self.0 % 12).abs();
253 buf.append(format_args!("{years}-{months}"))?;
254
255 Ok(IsNull::No)
256 }
257
258 fn produces(&self) -> Option<ExaTypeInfo> {
259 let num_years = self.0 / 12;
260 let precision = num_years
261 .unsigned_abs()
262 .checked_ilog10()
263 .unwrap_or_default()
264 + 1;
265
266 Some(ExaDataType::IntervalYearToMonth(IntervalYearToMonth::new(precision)).into())
267 }
268
269 fn size_hint(&self) -> usize {
270 1 + IntervalYearToMonth::MAX_PRECISION as usize + 1 + 2
272 }
273}
274
275impl<'r> Decode<'r, Exasol> for Months {
276 fn decode(value: ExaValueRef<'r>) -> Result<Self, BoxDynError> {
277 let input = Cow::<str>::deserialize(value.value).map_err(Box::new)?;
278 let input_err_fn = || format!("could not parse {input} as INTERVAL YEAR TO MONTH");
279
280 let (years, months) = input.rsplit_once('-').ok_or_else(input_err_fn)?;
281
282 let years = years.parse::<i32>().map_err(Box::new)?;
283 let months = months.parse::<i32>().map_err(Box::new)?;
284
285 let total_months = if years.is_negative() {
288 years * 12 - months
289 } else {
290 years * 12 + months
291 };
292
293 Ok(Months::new(total_months))
294 }
295}
296
297impl From<Months> for chrono::Months {
298 fn from(value: Months) -> Self {
299 chrono::Months::new(value.0.unsigned_abs())
300 }
301}
302
303impl<Tz: TimeZone> Add<Months> for DateTime<Tz> {
304 type Output = DateTime<Tz>;
305
306 fn add(self, rhs: Months) -> Self::Output {
307 if rhs.0.is_negative() {
308 self.checked_sub_months(rhs.into()).unwrap()
309 } else {
310 self.checked_add_months(rhs.into()).unwrap()
311 }
312 }
313}
314
315impl Add<Months> for NaiveDate {
316 type Output = NaiveDate;
317
318 fn add(self, rhs: Months) -> Self::Output {
319 if rhs.0.is_negative() {
320 self.checked_sub_months(rhs.into()).unwrap()
321 } else {
322 self.checked_add_months(rhs.into()).unwrap()
323 }
324 }
325}
326
327impl Add<Months> for NaiveDateTime {
328 type Output = NaiveDateTime;
329
330 fn add(self, rhs: Months) -> Self::Output {
331 if rhs.0.is_negative() {
332 self.checked_sub_months(rhs.into()).unwrap()
333 } else {
334 self.checked_add_months(rhs.into()).unwrap()
335 }
336 }
337}
338
339impl<Tz: TimeZone> Sub<Months> for DateTime<Tz> {
340 type Output = DateTime<Tz>;
341
342 fn sub(self, rhs: Months) -> Self::Output {
343 if rhs.0.is_negative() {
344 self.checked_add_months(rhs.into()).unwrap()
345 } else {
346 self.checked_sub_months(rhs.into()).unwrap()
347 }
348 }
349}
350
351impl Sub<Months> for NaiveDate {
352 type Output = NaiveDate;
353
354 fn sub(self, rhs: Months) -> Self::Output {
355 if rhs.0.is_negative() {
356 self.checked_add_months(rhs.into()).unwrap()
357 } else {
358 self.checked_sub_months(rhs.into()).unwrap()
359 }
360 }
361}
362
363impl Sub<Months> for NaiveDateTime {
364 type Output = NaiveDateTime;
365
366 fn sub(self, rhs: Months) -> Self::Output {
367 if rhs.0.is_negative() {
368 self.checked_add_months(rhs.into()).unwrap()
369 } else {
370 self.checked_sub_months(rhs.into()).unwrap()
371 }
372 }
373}