1use std::fmt;
4use std::num::ParseFloatError;
5
6use crate::Error;
7use crate::Result;
8
9pub const CAST_REASON_OVERFLOW: &str = "overflow";
11pub const CAST_REASON_PRECISION_LOSS: &str = "precision_loss";
13pub const CAST_REASON_INVALID_FORMAT: &str = "invalid_format";
15pub const CAST_REASON_NON_FINITE: &str = "non_finite";
17pub const CAST_REASON_UNSUPPORTED: &str = "unsupported";
19
20const MICROS_PER_SECOND: i64 = 1_000_000;
21const SECONDS_PER_DAY: i64 = 86_400;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum DataType {
26 Null,
28 Int32,
30 Int64,
32 Float32,
34 Float64,
36 Utf8,
38 TimestampMicros,
40}
41
42impl DataType {
43 pub fn as_str(self) -> &'static str {
45 match self {
46 Self::Null => "Null",
47 Self::Int32 => "Int32",
48 Self::Int64 => "Int64",
49 Self::Float32 => "Float32",
50 Self::Float64 => "Float64",
51 Self::Utf8 => "Utf8",
52 Self::TimestampMicros => "TimestampMicros",
53 }
54 }
55}
56
57impl fmt::Display for DataType {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.write_str(self.as_str())
60 }
61}
62
63#[derive(Clone, Debug, PartialEq)]
65pub enum DataValue {
66 Null,
68 Int32(i32),
70 Int64(i64),
72 Float32(f32),
74 Float64(f64),
76 Utf8(String),
78 TimestampMicros(i64),
80}
81
82impl DataValue {
83 pub fn data_type(&self) -> DataType {
85 match self {
86 Self::Null => DataType::Null,
87 Self::Int32(_) => DataType::Int32,
88 Self::Int64(_) => DataType::Int64,
89 Self::Float32(_) => DataType::Float32,
90 Self::Float64(_) => DataType::Float64,
91 Self::Utf8(_) => DataType::Utf8,
92 Self::TimestampMicros(_) => DataType::TimestampMicros,
93 }
94 }
95}
96
97pub fn can_cast(from: DataType, to: DataType) -> bool {
99 if from == to || from == DataType::Null {
100 return true;
101 }
102
103 matches!(
104 (from, to),
105 (_, DataType::Utf8)
106 | (
107 DataType::Utf8,
108 DataType::Int32
109 | DataType::Int64
110 | DataType::Float32
111 | DataType::Float64
112 | DataType::TimestampMicros,
113 )
114 | (
115 DataType::Int32 | DataType::Int64 | DataType::Float32 | DataType::Float64,
116 DataType::Int32
117 | DataType::Int64
118 | DataType::Float32
119 | DataType::Float64
120 | DataType::TimestampMicros,
121 )
122 | (
123 DataType::TimestampMicros,
124 DataType::Int32 | DataType::Int64 | DataType::Float32 | DataType::Float64,
125 )
126 )
127}
128
129pub fn cast_value(value: &DataValue, to_type: DataType) -> Result<DataValue> {
131 let from_type = value.data_type();
132 if !can_cast(from_type, to_type) {
133 return Err(cast_failed(from_type, to_type, CAST_REASON_UNSUPPORTED));
134 }
135
136 if matches!(value, DataValue::Null) {
137 return Ok(DataValue::Null);
138 }
139
140 if value.data_type() == to_type {
141 return Ok(value.clone());
142 }
143
144 match to_type {
145 DataType::Null => Ok(DataValue::Null),
146 DataType::Int32 => cast_to_int32(value),
147 DataType::Int64 => cast_to_int64(value),
148 DataType::Float32 => cast_to_float32(value),
149 DataType::Float64 => cast_to_float64(value),
150 DataType::Utf8 => Ok(DataValue::Utf8(value_to_string(value))),
151 DataType::TimestampMicros => cast_to_timestamp_micros(value),
152 }
153}
154
155fn cast_to_int32(value: &DataValue) -> Result<DataValue> {
156 let from_type = value.data_type();
157 let parsed = match value {
158 DataValue::Int64(v) => i32::try_from(*v)
159 .map_err(|_| cast_failed(from_type, DataType::Int32, CAST_REASON_OVERFLOW))?,
160 DataValue::Float32(v) => f64_to_i32(f64::from(*v), from_type)?,
161 DataValue::Float64(v) => f64_to_i32(*v, from_type)?,
162 DataValue::Utf8(v) => v
163 .trim()
164 .parse::<i32>()
165 .map_err(|_| cast_failed(from_type, DataType::Int32, CAST_REASON_INVALID_FORMAT))?,
166 DataValue::TimestampMicros(v) => {
167 micros_to_i64_epoch_seconds(*v, from_type).and_then(|seconds| {
168 i32::try_from(seconds)
169 .map_err(|_| cast_failed(from_type, DataType::Int32, CAST_REASON_OVERFLOW))
170 })?
171 }
172 DataValue::Int32(v) => *v,
173 DataValue::Null => unreachable!("null is handled before typed casts"),
174 };
175 Ok(DataValue::Int32(parsed))
176}
177
178fn cast_to_int64(value: &DataValue) -> Result<DataValue> {
179 let from_type = value.data_type();
180 let parsed = match value {
181 DataValue::Int32(v) => i64::from(*v),
182 DataValue::Float32(v) => f64_to_i64(f64::from(*v), from_type)?,
183 DataValue::Float64(v) => f64_to_i64(*v, from_type)?,
184 DataValue::Utf8(v) => v
185 .trim()
186 .parse::<i64>()
187 .map_err(|_| cast_failed(from_type, DataType::Int64, CAST_REASON_INVALID_FORMAT))?,
188 DataValue::TimestampMicros(v) => micros_to_i64_epoch_seconds(*v, from_type)?,
189 DataValue::Int64(v) => *v,
190 DataValue::Null => unreachable!("null is handled before typed casts"),
191 };
192 Ok(DataValue::Int64(parsed))
193}
194
195fn cast_to_float32(value: &DataValue) -> Result<DataValue> {
196 let from_type = value.data_type();
197 let parsed = match value {
198 DataValue::Int32(v) => i64_to_f32(i64::from(*v), from_type)?,
199 DataValue::Int64(v) => i64_to_f32(*v, from_type)?,
200 DataValue::Float64(v) => f64_to_f32(*v, from_type)?,
201 DataValue::Utf8(v) => parse_f32(v.trim(), from_type)?,
202 DataValue::TimestampMicros(v) => {
203 f64_to_f32(micros_to_f64_epoch_seconds(*v, from_type)?, from_type)?
204 }
205 DataValue::Float32(v) => *v,
206 DataValue::Null => unreachable!("null is handled before typed casts"),
207 };
208 Ok(DataValue::Float32(parsed))
209}
210
211fn cast_to_float64(value: &DataValue) -> Result<DataValue> {
212 let from_type = value.data_type();
213 let parsed = match value {
214 DataValue::Int32(v) => f64::from(*v),
215 DataValue::Int64(v) => i64_to_f64(*v, from_type)?,
216 DataValue::Float32(v) => {
217 if v.is_finite() {
218 f64::from(*v)
219 } else {
220 return Err(cast_failed(
221 from_type,
222 DataType::Float64,
223 CAST_REASON_NON_FINITE,
224 ));
225 }
226 }
227 DataValue::Utf8(v) => parse_f64(v.trim(), from_type)?,
228 DataValue::TimestampMicros(v) => micros_to_f64_epoch_seconds(*v, from_type)?,
229 DataValue::Float64(v) => *v,
230 DataValue::Null => unreachable!("null is handled before typed casts"),
231 };
232 Ok(DataValue::Float64(parsed))
233}
234
235fn cast_to_timestamp_micros(value: &DataValue) -> Result<DataValue> {
236 let from_type = value.data_type();
237 let micros = match value {
238 DataValue::Int32(v) => epoch_seconds_to_micros(i64::from(*v), from_type)?,
239 DataValue::Int64(v) => epoch_seconds_to_micros(*v, from_type)?,
240 DataValue::Float32(v) => f64_epoch_seconds_to_micros(f64::from(*v), from_type)?,
241 DataValue::Float64(v) => f64_epoch_seconds_to_micros(*v, from_type)?,
242 DataValue::Utf8(v) => parse_timestamp_micros(v.trim(), from_type)?,
243 DataValue::TimestampMicros(v) => *v,
244 DataValue::Null => unreachable!("null is handled before typed casts"),
245 };
246 Ok(DataValue::TimestampMicros(micros))
247}
248
249fn value_to_string(value: &DataValue) -> String {
250 match value {
251 DataValue::Null => "NULL".to_string(),
252 DataValue::Int32(v) => v.to_string(),
253 DataValue::Int64(v) => v.to_string(),
254 DataValue::Float32(v) => v.to_string(),
255 DataValue::Float64(v) => v.to_string(),
256 DataValue::Utf8(v) => v.clone(),
257 DataValue::TimestampMicros(v) => format_timestamp_micros(*v),
258 }
259}
260
261fn parse_f32(input: &str, from_type: DataType) -> Result<f32> {
262 let parsed = input
263 .parse::<f32>()
264 .map_err(|err| parse_float_error(from_type, DataType::Float32, err))?;
265 if parsed.is_finite() {
266 Ok(parsed)
267 } else {
268 Err(cast_failed(
269 from_type,
270 DataType::Float32,
271 CAST_REASON_NON_FINITE,
272 ))
273 }
274}
275
276fn parse_f64(input: &str, from_type: DataType) -> Result<f64> {
277 let parsed = input
278 .parse::<f64>()
279 .map_err(|err| parse_float_error(from_type, DataType::Float64, err))?;
280 if parsed.is_finite() {
281 Ok(parsed)
282 } else {
283 Err(cast_failed(
284 from_type,
285 DataType::Float64,
286 CAST_REASON_NON_FINITE,
287 ))
288 }
289}
290
291fn parse_float_error(from_type: DataType, to_type: DataType, _err: ParseFloatError) -> Error {
292 cast_failed(from_type, to_type, CAST_REASON_INVALID_FORMAT)
293}
294
295fn f64_to_i32(value: f64, from_type: DataType) -> Result<i32> {
296 ensure_finite(value, from_type, DataType::Int32)?;
297 if value.fract() != 0.0 {
298 return Err(cast_failed(
299 from_type,
300 DataType::Int32,
301 CAST_REASON_PRECISION_LOSS,
302 ));
303 }
304 if value < f64::from(i32::MIN) || value > f64::from(i32::MAX) {
305 return Err(cast_failed(
306 from_type,
307 DataType::Int32,
308 CAST_REASON_OVERFLOW,
309 ));
310 }
311 Ok(value as i32)
312}
313
314fn f64_to_i64(value: f64, from_type: DataType) -> Result<i64> {
315 ensure_finite(value, from_type, DataType::Int64)?;
316 if value.fract() != 0.0 {
317 return Err(cast_failed(
318 from_type,
319 DataType::Int64,
320 CAST_REASON_PRECISION_LOSS,
321 ));
322 }
323 if value < i64::MIN as f64 || value >= 9_223_372_036_854_775_808.0 {
324 return Err(cast_failed(
325 from_type,
326 DataType::Int64,
327 CAST_REASON_OVERFLOW,
328 ));
329 }
330 let casted = value as i64;
331 if casted as f64 != value {
332 return Err(cast_failed(
333 from_type,
334 DataType::Int64,
335 CAST_REASON_PRECISION_LOSS,
336 ));
337 }
338 Ok(casted)
339}
340
341fn f64_to_f32(value: f64, from_type: DataType) -> Result<f32> {
342 ensure_finite(value, from_type, DataType::Float32)?;
343 let casted = value as f32;
344 if !casted.is_finite() {
345 return Err(cast_failed(
346 from_type,
347 DataType::Float32,
348 CAST_REASON_OVERFLOW,
349 ));
350 }
351 if f64::from(casted) != value {
352 return Err(cast_failed(
353 from_type,
354 DataType::Float32,
355 CAST_REASON_PRECISION_LOSS,
356 ));
357 }
358 Ok(casted)
359}
360
361fn i64_to_f32(value: i64, from_type: DataType) -> Result<f32> {
362 if !integer_exact_in_float(value.unsigned_abs(), 24) {
363 return Err(cast_failed(
364 from_type,
365 DataType::Float32,
366 CAST_REASON_PRECISION_LOSS,
367 ));
368 }
369 Ok(value as f32)
370}
371
372fn i64_to_f64(value: i64, from_type: DataType) -> Result<f64> {
373 if !integer_exact_in_float(value.unsigned_abs(), 53) {
374 return Err(cast_failed(
375 from_type,
376 DataType::Float64,
377 CAST_REASON_PRECISION_LOSS,
378 ));
379 }
380 Ok(value as f64)
381}
382
383fn integer_exact_in_float(abs_value: u64, precision_bits: u32) -> bool {
384 if abs_value == 0 {
385 return true;
386 }
387 let bit_width = u64::BITS - abs_value.leading_zeros();
388 bit_width <= precision_bits || abs_value.trailing_zeros() >= bit_width - precision_bits
389}
390
391fn ensure_finite(value: f64, from_type: DataType, to_type: DataType) -> Result<()> {
392 if value.is_finite() {
393 Ok(())
394 } else {
395 Err(cast_failed(from_type, to_type, CAST_REASON_NON_FINITE))
396 }
397}
398
399fn epoch_seconds_to_micros(seconds: i64, from_type: DataType) -> Result<i64> {
400 seconds
401 .checked_mul(MICROS_PER_SECOND)
402 .ok_or_else(|| cast_failed(from_type, DataType::TimestampMicros, CAST_REASON_OVERFLOW))
403}
404
405fn f64_epoch_seconds_to_micros(seconds: f64, from_type: DataType) -> Result<i64> {
406 ensure_finite(seconds, from_type, DataType::TimestampMicros)?;
407 let micros = seconds * MICROS_PER_SECOND as f64;
408 ensure_finite(micros, from_type, DataType::TimestampMicros)?;
409 if micros.fract() != 0.0 {
410 return Err(cast_failed(
411 from_type,
412 DataType::TimestampMicros,
413 CAST_REASON_PRECISION_LOSS,
414 ));
415 }
416 if micros < i64::MIN as f64 || micros >= 9_223_372_036_854_775_808.0 {
417 return Err(cast_failed(
418 from_type,
419 DataType::TimestampMicros,
420 CAST_REASON_OVERFLOW,
421 ));
422 }
423 Ok(micros as i64)
424}
425
426fn micros_to_i64_epoch_seconds(micros: i64, from_type: DataType) -> Result<i64> {
427 if micros % MICROS_PER_SECOND != 0 {
428 return Err(cast_failed(
429 from_type,
430 DataType::Int64,
431 CAST_REASON_PRECISION_LOSS,
432 ));
433 }
434 Ok(micros / MICROS_PER_SECOND)
435}
436
437fn micros_to_f64_epoch_seconds(micros: i64, from_type: DataType) -> Result<f64> {
438 let micros = i64_to_f64(micros, from_type)?;
439 Ok(micros / MICROS_PER_SECOND as f64)
440}
441
442fn parse_timestamp_micros(input: &str, from_type: DataType) -> Result<i64> {
443 let input = input.strip_suffix('Z').unwrap_or(input);
444 let (date, time) = input
445 .split_once('T')
446 .or_else(|| input.split_once(' '))
447 .ok_or_else(|| {
448 cast_failed(
449 from_type,
450 DataType::TimestampMicros,
451 CAST_REASON_INVALID_FORMAT,
452 )
453 })?;
454
455 let mut date_parts = date.split('-');
456 let year = parse_date_part::<i32>(date_parts.next(), from_type)?;
457 let month = parse_date_part::<u32>(date_parts.next(), from_type)?;
458 let day = parse_date_part::<u32>(date_parts.next(), from_type)?;
459 if date_parts.next().is_some() || !valid_date(year, month, day) {
460 return Err(cast_failed(
461 from_type,
462 DataType::TimestampMicros,
463 CAST_REASON_INVALID_FORMAT,
464 ));
465 }
466
467 let mut time_parts = time.split(':');
468 let hour = parse_date_part::<u32>(time_parts.next(), from_type)?;
469 let minute = parse_date_part::<u32>(time_parts.next(), from_type)?;
470 let second_text = time_parts.next().ok_or_else(|| {
471 cast_failed(
472 from_type,
473 DataType::TimestampMicros,
474 CAST_REASON_INVALID_FORMAT,
475 )
476 })?;
477 if time_parts.next().is_some() {
478 return Err(cast_failed(
479 from_type,
480 DataType::TimestampMicros,
481 CAST_REASON_INVALID_FORMAT,
482 ));
483 }
484
485 let (second, micros) = parse_second_micros(second_text, from_type)?;
486 if hour > 23 || minute > 59 || second > 59 {
487 return Err(cast_failed(
488 from_type,
489 DataType::TimestampMicros,
490 CAST_REASON_INVALID_FORMAT,
491 ));
492 }
493
494 let days = days_from_civil(year, month, day);
495 let seconds = days
496 .checked_mul(SECONDS_PER_DAY)
497 .and_then(|v| v.checked_add(i64::from(hour) * 3_600))
498 .and_then(|v| v.checked_add(i64::from(minute) * 60))
499 .and_then(|v| v.checked_add(i64::from(second)))
500 .ok_or_else(|| cast_failed(from_type, DataType::TimestampMicros, CAST_REASON_OVERFLOW))?;
501 seconds
502 .checked_mul(MICROS_PER_SECOND)
503 .and_then(|v| v.checked_add(i64::from(micros)))
504 .ok_or_else(|| cast_failed(from_type, DataType::TimestampMicros, CAST_REASON_OVERFLOW))
505}
506
507fn parse_date_part<T>(part: Option<&str>, from_type: DataType) -> Result<T>
508where
509 T: std::str::FromStr,
510{
511 part.and_then(|text| text.parse::<T>().ok()).ok_or_else(|| {
512 cast_failed(
513 from_type,
514 DataType::TimestampMicros,
515 CAST_REASON_INVALID_FORMAT,
516 )
517 })
518}
519
520fn parse_second_micros(input: &str, from_type: DataType) -> Result<(u32, u32)> {
521 let (second, fraction) = input.split_once('.').unwrap_or((input, ""));
522 let second = second.parse::<u32>().map_err(|_| {
523 cast_failed(
524 from_type,
525 DataType::TimestampMicros,
526 CAST_REASON_INVALID_FORMAT,
527 )
528 })?;
529 if fraction.is_empty() {
530 return Ok((second, 0));
531 }
532 if !fraction.bytes().all(|byte| byte.is_ascii_digit()) {
533 return Err(cast_failed(
534 from_type,
535 DataType::TimestampMicros,
536 CAST_REASON_INVALID_FORMAT,
537 ));
538 }
539 if fraction.len() > 6 {
540 return Err(cast_failed(
541 from_type,
542 DataType::TimestampMicros,
543 CAST_REASON_PRECISION_LOSS,
544 ));
545 }
546
547 let mut micros = fraction.parse::<u32>().map_err(|_| {
548 cast_failed(
549 from_type,
550 DataType::TimestampMicros,
551 CAST_REASON_INVALID_FORMAT,
552 )
553 })?;
554 for _ in fraction.len()..6 {
555 micros *= 10;
556 }
557 Ok((second, micros))
558}
559
560fn format_timestamp_micros(micros: i64) -> String {
561 let seconds = micros.div_euclid(MICROS_PER_SECOND);
562 let micros_remainder = micros.rem_euclid(MICROS_PER_SECOND);
563 let days = seconds.div_euclid(SECONDS_PER_DAY);
564 let second_of_day = seconds.rem_euclid(SECONDS_PER_DAY);
565 let (year, month, day) = civil_from_days(days);
566 let hour = second_of_day / 3_600;
567 let minute = (second_of_day % 3_600) / 60;
568 let second = second_of_day % 60;
569
570 if micros_remainder == 0 {
571 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z")
572 } else {
573 let mut fraction = format!("{micros_remainder:06}");
574 while fraction.ends_with('0') {
575 fraction.pop();
576 }
577 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{fraction}Z")
578 }
579}
580
581fn valid_date(year: i32, month: u32, day: u32) -> bool {
582 (1..=12).contains(&month) && (1..=days_in_month(year, month)).contains(&day)
583}
584
585fn days_in_month(year: i32, month: u32) -> u32 {
586 match month {
587 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
588 4 | 6 | 9 | 11 => 30,
589 2 if is_leap_year(year) => 29,
590 2 => 28,
591 _ => 0,
592 }
593}
594
595fn is_leap_year(year: i32) -> bool {
596 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
597}
598
599fn days_from_civil(year: i32, month: u32, day: u32) -> i64 {
600 let year = i64::from(year) - i64::from(month <= 2);
601 let era = if year >= 0 { year } else { year - 399 } / 400;
602 let year_of_era = year - era * 400;
603 let shifted_month = i64::from(month) + if month > 2 { -3 } else { 9 };
604 let day_of_year = (153 * shifted_month + 2) / 5 + i64::from(day) - 1;
605 let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
606 era * 146_097 + day_of_era - 719_468
607}
608
609fn civil_from_days(days: i64) -> (i64, i64, i64) {
610 let days = days + 719_468;
611 let era = if days >= 0 { days } else { days - 146_096 } / 146_097;
612 let day_of_era = days - era * 146_097;
613 let year_of_era =
614 (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
615 let mut year = year_of_era + era * 400;
616 let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
617 let month_prime = (5 * day_of_year + 2) / 153;
618 let day = day_of_year - (153 * month_prime + 2) / 5 + 1;
619 let month = month_prime + if month_prime < 10 { 3 } else { -9 };
620 year += i64::from(month <= 2);
621 (year, month, day)
622}
623
624fn cast_failed(from_type: DataType, to_type: DataType, reason: &str) -> Error {
625 Error::CastFailed {
626 from_type: from_type.as_str().to_string(),
627 to_type: to_type.as_str().to_string(),
628 reason: reason.to_string(),
629 }
630}
631
632#[cfg(test)]
633mod tests {
634 use super::{can_cast, cast_value, DataType, DataValue};
635 use crate::Error;
636
637 fn assert_cast(input: DataValue, target: DataType, expected: DataValue) {
638 assert_eq!(cast_value(&input, target).unwrap(), expected);
639 }
640
641 fn assert_cast_error(input: DataValue, target: DataType, reason: &str) {
642 let err = cast_value(&input, target).unwrap_err();
643 match err {
644 Error::CastFailed {
645 from_type,
646 to_type,
647 reason: actual,
648 } => {
649 assert_eq!(from_type, input.data_type().as_str());
650 assert_eq!(to_type, target.as_str());
651 assert_eq!(actual, reason);
652 }
653 other => panic!("expected CastFailed, got {other:?}"),
654 }
655 }
656
657 #[test]
658 fn cast_matrix_marks_supported_major_paths() {
659 assert!(can_cast(DataType::Int32, DataType::Int64));
660 assert!(can_cast(DataType::Int32, DataType::Utf8));
661 assert!(can_cast(DataType::Utf8, DataType::Float64));
662 assert!(can_cast(DataType::Utf8, DataType::TimestampMicros));
663 assert!(can_cast(DataType::TimestampMicros, DataType::Int64));
664 assert!(can_cast(DataType::Float64, DataType::TimestampMicros));
665 assert!(can_cast(DataType::Null, DataType::TimestampMicros));
666 }
667
668 #[test]
669 fn numeric_casts_cover_widening_and_checked_narrowing() {
670 assert_cast(DataValue::Int32(42), DataType::Int64, DataValue::Int64(42));
671 assert_cast(
672 DataValue::Int64(16_777_216),
673 DataType::Float32,
674 DataValue::Float32(16_777_216.0),
675 );
676 assert_cast(
677 DataValue::Float64(42.0),
678 DataType::Int32,
679 DataValue::Int32(42),
680 );
681 assert_cast(
682 DataValue::Float32(1.25),
683 DataType::Float64,
684 DataValue::Float64(1.25),
685 );
686 }
687
688 #[test]
689 fn numeric_casts_report_stable_error_codes() {
690 assert_cast_error(
691 DataValue::Int64(i64::from(i32::MAX) + 1),
692 DataType::Int32,
693 "overflow",
694 );
695 assert_cast_error(DataValue::Float64(42.5), DataType::Int64, "precision_loss");
696 assert_cast_error(DataValue::Float64(f64::NAN), DataType::Int64, "non_finite");
697 assert_cast_error(
698 DataValue::Int64(16_777_217),
699 DataType::Float32,
700 "precision_loss",
701 );
702 }
703
704 #[test]
705 fn string_numeric_casts_parse_and_stringify() {
706 assert_cast(
707 DataValue::Utf8(" -12 ".into()),
708 DataType::Int32,
709 DataValue::Int32(-12),
710 );
711 assert_cast(
712 DataValue::Utf8("3.5".into()),
713 DataType::Float64,
714 DataValue::Float64(3.5),
715 );
716 assert_cast(
717 DataValue::Int64(99),
718 DataType::Utf8,
719 DataValue::Utf8("99".into()),
720 );
721 assert_cast_error(
722 DataValue::Utf8("nan".into()),
723 DataType::Int64,
724 "invalid_format",
725 );
726 }
727
728 #[test]
729 fn timestamp_string_and_epoch_second_casts_are_stable() {
730 assert_cast(
731 DataValue::Utf8("1970-01-01T00:00:01Z".into()),
732 DataType::TimestampMicros,
733 DataValue::TimestampMicros(1_000_000),
734 );
735 assert_cast(
736 DataValue::Utf8("1970-01-01 00:00:01.250000".into()),
737 DataType::TimestampMicros,
738 DataValue::TimestampMicros(1_250_000),
739 );
740 assert_cast(
741 DataValue::TimestampMicros(1_250_000),
742 DataType::Float64,
743 DataValue::Float64(1.25),
744 );
745 assert_cast(
746 DataValue::Float64(1.25),
747 DataType::TimestampMicros,
748 DataValue::TimestampMicros(1_250_000),
749 );
750 assert_cast(
751 DataValue::TimestampMicros(1_000_000),
752 DataType::Utf8,
753 DataValue::Utf8("1970-01-01T00:00:01Z".into()),
754 );
755 assert_cast_error(
756 DataValue::Utf8("1970-13-01T00:00:00Z".into()),
757 DataType::TimestampMicros,
758 "invalid_format",
759 );
760 }
761
762 #[test]
763 fn timestamp_micros_to_float64_rejects_precision_loss() {
764 assert_cast_error(
765 DataValue::TimestampMicros((1_i64 << 53) + 1),
766 DataType::Float64,
767 "precision_loss",
768 );
769 }
770
771 #[test]
772 fn null_casts_to_any_supported_type_without_changing_value() {
773 assert_cast(DataValue::Null, DataType::Int64, DataValue::Null);
774 assert_cast(DataValue::Null, DataType::Utf8, DataValue::Null);
775 assert_cast(DataValue::Null, DataType::TimestampMicros, DataValue::Null);
776 }
777}