Skip to content

Commit 891998c

Browse files
mysql_srv: output mysql datetime/timestas as TimestampTZ
We have out own implementation of Datetime/Timestamp called TimestampTZ. This object has a 3 bytes bitmap that gives us better control over printing date only, timestamp with tz even if tz is zero and microsecond precision even if it is zero. Currently we were converting the TimestampTZ to NaiveDateTime and then printing it. This was causing some issues with MySQL and datetime. NaiveDateTime has an inner object called NaiveTime to represent time. When printing naive time, if the microsecond is zero, it is not printed [0]. For MySQL, if the Datetime column has the optional microsecond precision set, we need to print the microsecond even if it is zero, causing a mismatch between the readyset and MySQL. This commit changes the display object for datetime/tz columns to TimestampTZ and implement text and binary protocol trait for it. Ref: REA-4490 Ref: #1309 [0]: https://github.com/chronotope/chrono/blob/v0.4.38/src/naive/time/mod.rs#L1520 Change-Id: I31301b20bebdd1bb33dbf6b79b84a8f7065dee80 Reviewed-on: https://gerrit.readyset.name/c/readyset/+/7655 Tested-by: Buildkite CI Reviewed-by: Michael Zink <michael.z@readyset.io>
1 parent 7fe18b1 commit 891998c

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

mysql-srv/src/value/encode.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::io::{self, Write};
22

33
use byteorder::{LittleEndian, WriteBytesExt};
44
use mysql_time::MySqlTime;
5+
use readyset_data::TimestampTz;
56

67
use crate::error::{other_error, OtherErrorKind};
78
use crate::myc::constants::{ColumnFlags, ColumnType};
@@ -506,6 +507,70 @@ impl ToMySqlValue for MySqlTime {
506507
}
507508
}
508509

510+
impl ToMySqlValue for TimestampTz {
511+
fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
512+
w.write_lenenc_str(self.to_string().as_bytes()).map(|_| ())
513+
}
514+
515+
fn to_mysql_bin<W: Write>(&self, w: &mut W, c: &Column) -> io::Result<()> {
516+
let ts = self.to_chrono();
517+
match c.coltype {
518+
ColumnType::MYSQL_TYPE_DATETIME | ColumnType::MYSQL_TYPE_TIMESTAMP => {
519+
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html
520+
// To save space the packet can be compressed:
521+
// if year, month, day, hour, minutes, seconds and microseconds are all 0, length is
522+
// 0 and no other field is sent. if hour, seconds and microseconds
523+
// are all 0, length is 4 and no other field is sent.
524+
// if microseconds is 0, length is 7 and micro_seconds is not sent.
525+
// otherwise the length is 11
526+
let us = ts.nanosecond() / 1_000;
527+
let packet_len = if us != 0 {
528+
11
529+
} else if (ts.hour(), ts.minute(), ts.second()) != (0, 0, 0) {
530+
7
531+
} else if (ts.year(), ts.month(), ts.day()) != (0, 0, 0) {
532+
4
533+
} else {
534+
0
535+
};
536+
537+
w.write_u8(packet_len)?;
538+
539+
if packet_len == 0 {
540+
return Ok(()); // no need to write anything else
541+
}
542+
543+
w.write_u16::<LittleEndian>(ts.year() as u16)?;
544+
w.write_u8(ts.month() as u8)?;
545+
w.write_u8(ts.day() as u8)?;
546+
547+
if packet_len == 4 {
548+
return Ok(()); // no need to write time
549+
}
550+
551+
w.write_u8(ts.hour() as u8)?;
552+
w.write_u8(ts.minute() as u8)?;
553+
w.write_u8(ts.second() as u8)?;
554+
555+
if packet_len == 7 {
556+
return Ok(()); // no need to write microseconds
557+
}
558+
559+
w.write_u32::<LittleEndian>(us)?;
560+
561+
Ok(())
562+
}
563+
ColumnType::MYSQL_TYPE_DATE => {
564+
if ts.time() != NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
565+
return Err(bad(self, c));
566+
}
567+
ts.date_naive().to_mysql_bin(w, c)
568+
}
569+
_ => Err(bad(self, c)),
570+
}
571+
}
572+
}
573+
509574
impl ToMySqlValue for NaiveDateTime {
510575
fn to_mysql_text<W: Write>(&self, w: &mut W) -> io::Result<()> {
511576
let us = self.nanosecond() / 1_000;

readyset-mysql/src/backend.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,7 @@ async fn write_column<W: AsyncWrite + Unpin>(
139139
mysql_srv::ColumnType::MYSQL_TYPE_DATETIME
140140
| mysql_srv::ColumnType::MYSQL_TYPE_DATETIME2
141141
| mysql_srv::ColumnType::MYSQL_TYPE_TIMESTAMP
142-
| mysql_srv::ColumnType::MYSQL_TYPE_TIMESTAMP2 => {
143-
rw.write_col(ts.to_chrono().naive_local())
144-
}
142+
| mysql_srv::ColumnType::MYSQL_TYPE_TIMESTAMP2 => rw.write_col(ts),
145143
ColumnType::MYSQL_TYPE_DATE => rw.write_col(ts.to_chrono().naive_local().date()),
146144
_ => return Err(conv_error())?,
147145
},

0 commit comments

Comments
 (0)