Skip to content

Commit

Permalink
coprocessor: add a Convert trait and implement convert to decimal (ti…
Browse files Browse the repository at this point in the history
…kv#5167)

Signed-off-by: Lonng <heng@lonng.org>
  • Loading branch information
lonng authored and breezewish committed Aug 2, 2019
1 parent 71a5a77 commit c76e47e
Show file tree
Hide file tree
Showing 13 changed files with 286 additions and 187 deletions.
74 changes: 12 additions & 62 deletions components/tidb_query/src/codec/convert.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2016 TiKV Project Authors. Licensed under Apache-2.0.

use std::borrow::Cow;
use std::convert::TryFrom;
use std::{self, char, i16, i32, i64, i8, str, u16, u32, u64, u8};

use tidb_query_datatype::{self, FieldTypeTp};
Expand Down Expand Up @@ -358,7 +357,8 @@ impl ToInt for DateTime {
// TODO: avoid this clone after refactor the `Time`
let mut t = self.clone();
t.round_frac(DEFAULT_FSP)?;
let val = t.to_decimal()?.as_i64_with_ctx(ctx)?;
let dec: Decimal = t.convert(ctx)?;
let val = dec.as_i64_with_ctx(ctx)?;
val.to_int(ctx, tp)
}

Expand All @@ -367,22 +367,25 @@ impl ToInt for DateTime {
// TODO: avoid this clone after refactor the `Time`
let mut t = self.clone();
t.round_frac(DEFAULT_FSP)?;
decimal_as_u64(ctx, t.to_decimal()?, tp)
let dec: Decimal = t.convert(ctx)?;
decimal_as_u64(ctx, dec, tp)
}
}

impl ToInt for Duration {
#[inline]
fn to_int(&self, ctx: &mut EvalContext, tp: FieldTypeTp) -> Result<i64> {
let dur = (*self).round_frac(DEFAULT_FSP)?;
let val = Decimal::try_from(dur)?.as_i64_with_ctx(ctx)?;
let dec: Decimal = dur.convert(ctx)?;
let val = dec.as_i64_with_ctx(ctx)?;
val.to_int(ctx, tp)
}

#[inline]
fn to_uint(&self, ctx: &mut EvalContext, tp: FieldTypeTp) -> Result<u64> {
let dur = (*self).round_frac(DEFAULT_FSP)?;
decimal_as_u64(ctx, Decimal::try_from(dur)?, tp)
let dec: Decimal = dur.convert(ctx)?;
decimal_as_u64(ctx, dec, tp)
}
}

Expand Down Expand Up @@ -471,23 +474,6 @@ fn decimal_as_u64(ctx: &mut EvalContext, dec: Decimal, tp: FieldTypeTp) -> Resul
val.to_uint(ctx, tp)
}

/// Converts a bytes slice to a `Decimal`
#[inline]
pub fn convert_bytes_to_decimal(ctx: &mut EvalContext, bytes: &[u8]) -> Result<Decimal> {
let dec = match Decimal::from_bytes(bytes)? {
Res::Ok(d) => d,
Res::Overflow(d) => {
ctx.handle_overflow(Error::overflow("DECIMAL", ""))?;
d
}
Res::Truncated(d) => {
ctx.handle_truncate(true)?;
d
}
};
Ok(dec)
}

/// `bytes_to_int_without_context` converts a byte arrays to an i64
/// in best effort, but without context.
pub fn bytes_to_int_without_context(bytes: &[u8]) -> Result<i64> {
Expand Down Expand Up @@ -548,12 +534,14 @@ pub fn bytes_to_uint_without_context(bytes: &[u8]) -> Result<u64> {
}

impl ConvertTo<f64> for i64 {
#[inline]
fn convert(&self, _: &mut EvalContext) -> Result<f64> {
Ok(*self as f64)
}
}

impl ConvertTo<f64> for u64 {
#[inline]
fn convert(&self, _: &mut EvalContext) -> Result<f64> {
Ok(*self as f64)
}
Expand Down Expand Up @@ -581,12 +569,14 @@ impl ConvertTo<f64> for &[u8] {
}

impl ConvertTo<f64> for std::borrow::Cow<'_, [u8]> {
#[inline]
fn convert(&self, ctx: &mut EvalContext) -> Result<f64> {
self.as_ref().convert(ctx)
}
}

impl ConvertTo<f64> for Bytes {
#[inline]
fn convert(&self, ctx: &mut EvalContext) -> Result<f64> {
self.as_slice().convert(ctx)
}
Expand Down Expand Up @@ -798,7 +788,6 @@ mod tests {
use std::{f64, i64, isize, u64};

use crate::codec::error::{ERR_DATA_OUT_OF_RANGE, WARN_DATA_TRUNCATED};
use crate::codec::mysql::decimal::{self, DIGITS_PER_WORD, WORD_BUF_LEN};
use crate::expr::Flag;
use crate::expr::{EvalConfig, EvalContext};

Expand Down Expand Up @@ -1480,45 +1469,6 @@ mod tests {
}
}

#[test]
fn test_convert_bytes_to_decimal() {
let cases: Vec<(&[u8], Decimal)> = vec![
(b"123456.1", Decimal::from_f64(123456.1).unwrap()),
(b"-123456.1", Decimal::from_f64(-123456.1).unwrap()),
(b"123456", Decimal::from(123456)),
(b"-123456", Decimal::from(-123456)),
];
let mut ctx = EvalContext::default();
for (s, expect) in cases {
let got = convert_bytes_to_decimal(&mut ctx, s).unwrap();
assert_eq!(got, expect, "from {:?}, expect: {} got: {}", s, expect, got);
}

// OVERFLOWING
let big = (0..85).map(|_| '9').collect::<String>();
let val = convert_bytes_to_decimal(&mut ctx, big.as_bytes());
assert!(
val.is_err(),
"expected error, but got {:?}",
val.unwrap().to_string()
);
assert_eq!(val.unwrap_err().code(), ERR_DATA_OUT_OF_RANGE);

// OVERFLOW_AS_WARNING
let mut ctx = EvalContext::new(Arc::new(EvalConfig::from_flag(Flag::OVERFLOW_AS_WARNING)));
let val = convert_bytes_to_decimal(&mut ctx, big.as_bytes()).unwrap();
let max = decimal::max_decimal(WORD_BUF_LEN * DIGITS_PER_WORD, 0);
assert_eq!(
val,
max,
"expect: {}, got: {}",
val.to_string(),
max.to_string()
);
assert_eq!(ctx.warnings.warning_cnt, 1);
assert_eq!(ctx.warnings.warnings[0].get_code(), ERR_DATA_OUT_OF_RANGE);
}

#[test]
fn test_bytes_to_f64() {
let tests: Vec<(&'static [u8], Option<f64>)> = vec![
Expand Down
42 changes: 21 additions & 21 deletions components/tidb_query/src/codec/datum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
use byteorder::WriteBytesExt;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display, Formatter};
use std::io::Write;
use std::str::FromStr;
use std::{i64, str};

use tidb_query_datatype::FieldTypeTp;
Expand Down Expand Up @@ -381,14 +379,14 @@ impl Datum {
Datum::Bytes(bs) => ConvertTo::<f64>::convert(&bs, ctx).map(From::from),
Datum::Time(t) => {
// if time has no precision, return int64
let dec = t.to_decimal()?;
let dec: Decimal = t.convert(ctx)?;
if t.get_fsp() == 0 {
return Ok(Datum::I64(dec.as_i64().unwrap()));
}
Ok(Datum::Dec(dec))
}
Datum::Dur(d) => {
let dec = Decimal::try_from(d)?;
let dec: Decimal = d.convert(ctx)?;
if d.fsp() == 0 {
return Ok(Datum::I64(dec.as_i64().unwrap()));
}
Expand All @@ -399,10 +397,11 @@ impl Datum {
}

/// Keep compatible with TiDB's `ToDecimal` function.
/// FIXME: the `EvalContext` should be passed by caller
pub fn into_dec(self) -> Result<Decimal> {
match self {
Datum::Time(t) => t.to_decimal(),
Datum::Dur(d) => Decimal::try_from(d).map_err(From::from),
Datum::Time(t) => t.convert(&mut EvalContext::default()),
Datum::Dur(d) => d.convert(&mut EvalContext::default()),
d => match d.coerce_to_dec()? {
Datum::Dec(d) => Ok(d),
d => Err(box_err!("failed to conver {} to decimal", d)),
Expand Down Expand Up @@ -471,10 +470,13 @@ impl Datum {
let dec = match self {
Datum::I64(i) => i.into(),
Datum::U64(u) => u.into(),
Datum::F64(f) => Decimal::from_f64(f)?,
Datum::F64(f) => {
// FIXME: the `EvalContext` should be passed from caller
f.convert(&mut EvalContext::default())?
}
Datum::Bytes(ref bs) => {
let s = box_try!(str::from_utf8(bs));
Decimal::from_str(s)?
// FIXME: the `EvalContext` should be passed from caller
bs.convert(&mut EvalContext::default())?
}
d @ Datum::Dec(_) => return Ok(d),
_ => return Err(box_err!("failed to convert {} to decimal", self)),
Expand All @@ -483,15 +485,11 @@ impl Datum {
}

/// Try its best effort to convert into a f64 datum.
fn coerce_to_f64(self) -> Result<Datum> {
fn coerce_to_f64(self, ctx: &mut EvalContext) -> Result<Datum> {
match self {
Datum::I64(i) => Ok(Datum::F64(i as f64)),
Datum::U64(u) => Ok(Datum::F64(u as f64)),
Datum::Dec(d) => {
// TODO: remove this function `coerce_to_f64`
let f = d.convert(&mut EvalContext::default())?;
Ok(Datum::F64(f))
}
Datum::Dec(d) => Ok(Datum::F64(d.convert(ctx)?)),
a => Ok(a),
}
}
Expand All @@ -500,11 +498,11 @@ impl Datum {
/// If left or right is F64, changes the both to F64.
/// Else if left or right is Decimal, changes the both to Decimal.
/// Keep compatible with TiDB's `CoerceDatum` function.
pub fn coerce(left: Datum, right: Datum) -> Result<(Datum, Datum)> {
pub fn coerce(ctx: &mut EvalContext, left: Datum, right: Datum) -> Result<(Datum, Datum)> {
let res = match (left, right) {
a @ (Datum::Dec(_), Datum::Dec(_)) | a @ (Datum::F64(_), Datum::F64(_)) => a,
(l @ Datum::F64(_), r) => (l, r.coerce_to_f64()?),
(l, r @ Datum::F64(_)) => (l.coerce_to_f64()?, r),
(l @ Datum::F64(_), r) => (l, r.coerce_to_f64(ctx)?),
(l, r @ Datum::F64(_)) => (l.coerce_to_f64(ctx)?, r),
(l @ Datum::Dec(_), r) => (l, r.coerce_to_dec()?),
(l, r @ Datum::Dec(_)) => (l.coerce_to_dec()?, r),
p => p,
Expand Down Expand Up @@ -1011,6 +1009,7 @@ mod tests {
use tikv_util::as_slice;

use std::cmp::Ordering;
use std::str::FromStr;
use std::sync::Arc;
use std::{i16, i32, i64, i8, u16, u32, u64, u8};

Expand Down Expand Up @@ -1049,7 +1048,7 @@ mod tests {
],
vec![
Datum::U64(1),
Datum::Dec(Decimal::from_f64(2.3).unwrap()),
Datum::Dec(2.3.convert(&mut EvalContext::default()).unwrap()),
Datum::Dec("-34".parse().unwrap()),
],
vec![
Expand Down Expand Up @@ -1654,7 +1653,7 @@ mod tests {
Some(true),
),
(
Datum::Dec(Decimal::from_f64(0.1415926).unwrap()),
Datum::Dec(0.1415926.convert(&mut EvalContext::default()).unwrap()),
Some(false),
),
(Datum::Dec(0u64.into()), Some(false)),
Expand Down Expand Up @@ -1751,8 +1750,9 @@ mod tests {
),
];

let mut ctx = EvalContext::default();
for (x, y, exp_x, exp_y) in cases {
let (res_x, res_y) = Datum::coerce(x, y).unwrap();
let (res_x, res_y) = Datum::coerce(&mut ctx, x, y).unwrap();
assert_eq!(res_x, exp_x);
assert_eq!(res_y, exp_y);
}
Expand Down
Loading

0 comments on commit c76e47e

Please sign in to comment.