Skip to content

Commit

Permalink
implement several mysql time related types json
Browse files Browse the repository at this point in the history
Signed-off-by: YangKeao <yangkeao@chunibyo.icu>
  • Loading branch information
YangKeao committed Sep 8, 2022
1 parent 7d36f34 commit dcfdcb0
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 68 deletions.
12 changes: 6 additions & 6 deletions components/tidb_query_datatype/src/codec/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,14 +510,14 @@ impl<'a> ToInt for JsonRef<'a> {
// TiDB: 5
// MySQL: 4
let val = match self.get_type() {
JsonType::Object | JsonType::Array | JsonType::Opaque => Ok(ctx
.handle_truncate_err(Error::truncated_wrong_val("Integer", self.to_string()))
.map(|_| 0)?),
JsonType::Literal => Ok(self.get_literal().map_or(0, |x| x as i64)),
JsonType::I64 => Ok(self.get_i64()),
JsonType::U64 => Ok(self.get_u64() as i64),
JsonType::Double => self.get_double().to_int(ctx, tp),
JsonType::String => self.get_str_bytes()?.to_int(ctx, tp),
_ => Ok(ctx
.handle_truncate_err(Error::truncated_wrong_val("Integer", self.to_string()))
.map(|_| 0)?),
}?;
val.to_int(ctx, tp)
}
Expand All @@ -526,14 +526,14 @@ impl<'a> ToInt for JsonRef<'a> {
#[inline]
fn to_uint(&self, ctx: &mut EvalContext, tp: FieldTypeTp) -> Result<u64> {
let val = match self.get_type() {
JsonType::Object | JsonType::Array | JsonType::Opaque => Ok(ctx
.handle_truncate_err(Error::truncated_wrong_val("Integer", self.to_string()))
.map(|_| 0)?),
JsonType::Literal => Ok(self.get_literal().map_or(0, |x| x as u64)),
JsonType::I64 => Ok(self.get_i64() as u64),
JsonType::U64 => Ok(self.get_u64()),
JsonType::Double => self.get_double().to_uint(ctx, tp),
JsonType::String => self.get_str_bytes()?.to_uint(ctx, tp),
_ => Ok(ctx
.handle_truncate_err(Error::truncated_wrong_val("Integer", self.to_string()))
.map(|_| 0)?),
}?;
val.to_uint(ctx, tp)
}
Expand Down
7 changes: 7 additions & 0 deletions components/tidb_query_datatype/src/codec/mysql/json/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ impl<'a> JsonRef<'a> {
&self.value()[val_offset..val_offset + opaque_bytes_len as usize + len_len + 1],
)
}
JsonType::Date | JsonType::Datetime | JsonType::Timestamp => {
JsonRef::new(val_type, &self.value()[val_offset..val_offset + TIME_LEN])
}
JsonType::Time => JsonRef::new(
val_type,
&self.value()[val_offset..val_offset + DURATION_LEN],
),
_ => {
let data_size =
NumberCodec::decode_u32_le(&self.value()[val_offset + ELEMENT_COUNT_LEN..])
Expand Down
21 changes: 21 additions & 0 deletions components/tidb_query_datatype/src/codec/mysql/json/comparison.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ impl<'a> JsonRef<'a> {
JsonType::I64 | JsonType::U64 | JsonType::Double => PRECEDENCE_NUMBER,
JsonType::String => PRECEDENCE_STRING,
JsonType::Opaque => PRECEDENCE_OPAQUE,
JsonType::Date => PRECEDENCE_DATE,
JsonType::Datetime => PRECEDENCE_DATETIME,
JsonType::Timestamp => PRECEDENCE_DATETIME,
JsonType::Time => PRECEDENCE_TIME,
}
}

Expand Down Expand Up @@ -150,6 +154,23 @@ impl<'a> PartialOrd for JsonRef<'a> {
return None;
}
}
JsonType::Date | JsonType::Datetime | JsonType::Timestamp => {
// The jsonTypePrecedences guarantees that the DATE is only comparable with the
// DATE, and the DATETIME and TIMESTAMP will compare with
// each other
if let (Ok(left), Ok(right)) = (self.get_time(), right.get_time()) {
left.partial_cmp(&right)
} else {
return None;
}
}
JsonType::Time => {
if let (Ok(left), Ok(right)) = (self.get_duration(), right.get_duration()) {
left.partial_cmp(&right)
} else {
return None;
}
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub const LITERAL_LEN: usize = 1;
pub const U16_LEN: usize = 2;
pub const U32_LEN: usize = 4;
pub const NUMBER_LEN: usize = 8;
pub const TIME_LEN: usize = NUMBER_LEN;
pub const DURATION_LEN: usize = NUMBER_LEN + U32_LEN;
pub const HEADER_LEN: usize = ELEMENT_COUNT_LEN + SIZE_LEN; // element size + data size
pub const KEY_OFFSET_LEN: usize = U32_LEN;
pub const KEY_LEN_LEN: usize = U16_LEN;
Expand Down
4 changes: 4 additions & 0 deletions components/tidb_query_datatype/src/codec/mysql/json/jcodec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ pub trait JsonDecoder: NumberDecoder {
let (opaque_bytes_len, len_len) = NumberCodec::try_decode_var_u64(&value[1..])?;
self.read_bytes(opaque_bytes_len as usize + len_len + 1)?
}
JsonType::Date | JsonType::Datetime | JsonType::Timestamp => {
self.read_bytes(TIME_LEN)?
}
JsonType::Time => self.read_bytes(DURATION_LEN)?,
};
Ok(Json::new(tp, Vec::from(value)))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const JSON_TYPE_ARRAY: &[u8] = b"ARRAY";
const JSON_TYPE_BIT: &[u8] = b"BIT";
const JSON_TYPE_BLOB: &[u8] = b"BLOB";
const JSON_TYPE_OPAQUE: &[u8] = b"OPAQUE";
const JSON_TYPE_DATE: &[u8] = b"DATE";
const JSON_TYPE_DATETIME: &[u8] = b"DATETIME";
const JSON_TYPE_TIME: &[u8] = b"TIME";

impl<'a> JsonRef<'a> {
/// `json_type` is the implementation for
Expand Down Expand Up @@ -43,6 +46,10 @@ impl<'a> JsonRef<'a> {
Ok(FieldTypeTp::Bit) => JSON_TYPE_BIT,
_ => JSON_TYPE_OPAQUE,
},
JsonType::Date => JSON_TYPE_DATE,
JsonType::Datetime => JSON_TYPE_DATETIME,
JsonType::Timestamp => JSON_TYPE_DATETIME,
JsonType::Time => JSON_TYPE_TIME,
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ impl<'a> JsonRef<'a> {
let s = self.get_str()?;
unquote_string(s)
}
JsonType::Date
| JsonType::Datetime
| JsonType::Timestamp
| JsonType::Time
| JsonType::Opaque => {
let s = self.to_string();
// remove the quotes of output
assert!(s.len() > 2);
Ok(s[1..s.len() - 1].to_string())
}
_ => Ok(self.to_string()),
}
}
Expand Down
96 changes: 71 additions & 25 deletions components/tidb_query_datatype/src/codec/mysql/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ mod json_remove;
mod json_type;
pub mod json_unquote;

use std::{collections::BTreeMap, convert::TryFrom, str};
use std::{
collections::BTreeMap,
convert::{TryFrom, TryInto},
str,
};

use codec::number::{NumberCodec, F64_SIZE, I64_SIZE};
use constants::{JSON_LITERAL_FALSE, JSON_LITERAL_NIL, JSON_LITERAL_TRUE};
Expand All @@ -91,7 +95,6 @@ use crate::{
codec::{
convert::ConvertTo,
data_type::{BytesRef, Decimal, Real},
mysql,
mysql::{Duration, Time, TimeType},
},
expr::EvalContext,
Expand All @@ -114,6 +117,10 @@ pub enum JsonType {
// It's a special value for the compatibility with MySQL.
// It will store the raw buffer containing unexpected type (e.g. Binary).
Opaque = 0x0d,
Date = 0x0e,
Datetime = 0x0f,
Timestamp = 0x10,
Time = 0x11,
}

impl TryFrom<u8> for JsonType {
Expand Down Expand Up @@ -225,19 +232,41 @@ impl<'a> JsonRef<'a> {
FieldTypeTp::from_u8(val[0]).ok_or(box_err!("invalid opaque type code"))
}

pub fn get_time(&self) -> Result<Time> {
assert!(
self.type_code == JsonType::Date
|| self.type_code == JsonType::Datetime
|| self.type_code == JsonType::Timestamp
);
let mut val = self.value();
val.read_time_from_chunk()
}

pub fn get_duration(&self) -> Result<Duration> {
assert_eq!(self.type_code, JsonType::Time);
let val = self.value();

let nanos = NumberCodec::decode_i64_le(val);
let fsp = NumberCodec::decode_u32_le(&val[8..])
.try_into()
.map_err(|_| -> Error { box_err!("invalid fsp") })?;
Duration::from_nanos(nanos, fsp)
}

// Return whether the value is zero.
// https://dev.mysql.com/doc/refman/8.0/en/json.html#Converting%20between%20JSON%20and%20non-JSON%20values
pub(crate) fn is_zero(&self) -> bool {
match self.get_type() {
JsonType::Object => false,
JsonType::Array => false,
JsonType::Literal => false,
JsonType::I64 => self.get_i64() == 0,
JsonType::U64 => self.get_u64() == 0,
JsonType::Double => self.get_double() == 0f64,
JsonType::String => false,
JsonType::Opaque => false,
}
// This behavior is different on MySQL 5.7 and 8.0
//
// In MySQL 5.7, most of these non-integer values are 0, and return a warning:
// "Invalid JSON value for CAST to INTEGER from column j"
//
// In MySQL 8, most of these non-integer values are not zero, with a warning:
// > "Evaluating a JSON value in SQL boolean context does an implicit comparison
// > against JSON integer 0; if this is not what you want, consider converting
// > JSON to a SQL numeric type with JSON_VALUE RETURNING"

self != &Json::from_u64(0).unwrap().as_ref()
}

// Returns whether the two JsonRef references to the same
Expand Down Expand Up @@ -269,6 +298,10 @@ pub struct Json {

use std::fmt::{Display, Formatter};

use codec::prelude::NumberEncoder;

use crate::codec::mysql::{TimeDecoder, TimeEncoder};

impl Display for Json {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_string(&self.as_ref()).unwrap();
Expand Down Expand Up @@ -371,6 +404,26 @@ impl Json {
Ok(Self::new(JsonType::Object, value))
}

/// Creates a date/datetime/timestamp JSON from Time
pub fn from_time(time: Time) -> Result<Self> {
let json_type = match time.get_time_type() {
TimeType::Date => JsonType::Date,
TimeType::DateTime => JsonType::Datetime,
TimeType::Timestamp => JsonType::Timestamp,
};
let mut value = vec![];
value.write_time(time)?;
Ok(Self::new(json_type, value))
}

/// Creates a time JSON from duration
pub fn from_duration(duration: Duration) -> Result<Self> {
let mut value = vec![];
value.write_i64_le(duration.to_nanos())?;
value.write_u32_le(duration.fsp() as u32)?;
Ok(Self::new(JsonType::Time, value))
}

/// Creates a `null` JSON
pub fn none() -> Result<Self> {
let mut value = vec![];
Expand Down Expand Up @@ -440,16 +493,16 @@ impl<'a> ConvertTo<f64> for JsonRef<'a> {
#[inline]
fn convert(&self, ctx: &mut EvalContext) -> Result<f64> {
let d = match self.get_type() {
JsonType::Array | JsonType::Object | JsonType::Opaque => ctx
.handle_truncate_err(Error::truncated_wrong_val("Float", self.to_string()))
.map(|_| 0f64)?,
JsonType::U64 => self.get_u64() as f64,
JsonType::I64 => self.get_i64() as f64,
JsonType::Double => self.get_double(),
JsonType::Literal => self
.get_literal()
.map_or(0f64, |x| if x { 1f64 } else { 0f64 }),
JsonType::String => self.get_str_bytes()?.convert(ctx)?,
_ => ctx
.handle_truncate_err(Error::truncated_wrong_val("Float", self.to_string()))
.map(|_| 0f64)?,
};
Ok(d)
}
Expand Down Expand Up @@ -507,22 +560,15 @@ impl ConvertTo<Json> for Decimal {

impl ConvertTo<Json> for Time {
#[inline]
fn convert(&self, ctx: &mut EvalContext) -> Result<Json> {
let tp = self.get_time_type();
let s = if tp == TimeType::DateTime || tp == TimeType::Timestamp {
self.round_frac(ctx, mysql::MAX_FSP)?
} else {
*self
};
Json::from_string(s.to_string())
fn convert(&self, _: &mut EvalContext) -> Result<Json> {
Json::from_time(*self)
}
}

impl ConvertTo<Json> for Duration {
#[inline]
fn convert(&self, _: &mut EvalContext) -> Result<Json> {
let d = self.maximize_fsp();
Json::from_string(d.to_string())
Json::from_duration(*self)
}
}

Expand Down
11 changes: 3 additions & 8 deletions components/tidb_query_datatype/src/codec/mysql/json/modifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,6 @@ impl<'a> BinaryModifier<'a> {
}
let tp = self.old.get_type();
match tp {
JsonType::Literal
| JsonType::I64
| JsonType::U64
| JsonType::Double
| JsonType::String
| JsonType::Opaque => {
buf.extend_from_slice(self.old.value);
}
JsonType::Object | JsonType::Array => {
let doc_off = buf.len();
let elem_count = self.old.get_elem_count();
Expand Down Expand Up @@ -303,6 +295,9 @@ impl<'a> BinaryModifier<'a> {
data_len as u32,
);
}
_ => {
buf.extend_from_slice(self.old.value);
}
}
Ok(tp)
}
Expand Down
18 changes: 18 additions & 0 deletions components/tidb_query_datatype/src/codec/mysql/json/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ impl<'a> Serialize for JsonRef<'a> {
let str = format!("base64:type{}:{}", typ, base64::encode(bytes));
serializer.serialize_str(&str)
}
JsonType::Date | JsonType::Datetime | JsonType::Timestamp => {
let mut time = self
.get_time()
.map_err(|_| SerError::custom("invalid time data"))?;
// Printing json datetime/duration will always keep 6 fsp
time.maximize_fsp();

serializer.serialize_str(&time.to_string())
}
JsonType::Time => {
let duration = self
.get_duration()
.map_err(|_| SerError::custom("invalid duration data"))?;
// Printing json datetime/duration will always keep 6 fsp
let duration = duration.maximize_fsp();

serializer.serialize_str(&duration.to_string())
}
}
}
}
Expand Down

0 comments on commit dcfdcb0

Please sign in to comment.