Skip to content

Support for timestamps with sub second resolution. #281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
11 changes: 10 additions & 1 deletion source/mysql/impl/prepared.d
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import mysql.protocol.packets;
import mysql.types;
import mysql.impl.result;
import mysql.safe.commands : ColumnSpecialization, CSN;
import std.datetime : DateTime;

/++
A struct to represent specializations of prepared statement parameters.
Expand Down Expand Up @@ -153,11 +154,19 @@ public:

enforce!MYX(index < _numParams, "Parameter index out of range.");

_inParams[index] = val;
static if ((is (T == DateTime)) || (is (T == const(DateTime)))|| (is (T == immutable(DateTime))))
{
_inParams[index] = DateTimeExt(val,0);
}
else
{
_inParams[index] = val;
}
psn.pIndex = index;
_psa[index] = psn;
}


///ditto
void setArg(T)(size_t index, Nullable!T val, SafeParameterSpecialization psn = SPSN.init)
{
Expand Down
58 changes: 38 additions & 20 deletions source/mysql/protocol/packet_helpers.d
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ ubyte[] pack(in Date dt) pure nothrow
}

/++
Function to extract a DateTime from a binary encoded row.
Function to extract a DateTimeExt from a binary encoded row.

Time/date structures are packed by the server into a byte sub-packet
with a leading length byte, and a minimal number of bytes to embody the data.
Expand All @@ -227,28 +227,31 @@ Params: a = slice of a protocol packet beginning at the length byte for a
chunk of DateTime data
Returns: A populated or default initialized `std.datetime.DateTime` struct.
+/
DateTime toDateTime(in ubyte[] a) pure
DateTimeExt toDateTime(in ubyte[] a) pure
{
enforce!MYXProtocol(a.length, "Supplied byte array is zero length");
if (a[0] == 0)
return DateTime();
return DateTimeExt(DateTimeExt(),0);

enforce!MYXProtocol(a[0] >= 4, "Supplied ubyte[] is not long enough");
int year = (a[2] << 8) + a[1];
int month = a[3];
int day = a[4];
DateTime dt;
DateTimeExt dt;
if (a[0] == 4)
{
dt = DateTime(year, month, day);
dt = DateTimeExt(DateTime(year, month, day),0);
}
else
{
enforce!MYXProtocol(a[0] >= 7, "Supplied ubyte[] is not long enough");
int hour = a[5];
int minute = a[6];
int second = a[7];
dt = DateTime(year, month, day, hour, minute, second);
int millisecond = 0;
if (a[0] >= 11)
millisecond = (a[11] << 24) + (a[10] << 16) + (a[9] << 8) + a[8];
dt = DateTimeExt(DateTime(year, month, day, hour, minute, second),millisecond);
}
return dt;
}
Expand All @@ -261,7 +264,7 @@ Text representations of a DateTime are as in 2011-11-11 12:20:02
Params: s = A string representation of the time difference.
Returns: A populated or default initialized `std.datetime.DateTime` struct.
+/
DateTime toDateTime(const(char)[] s)
DateTimeExt toDateTime(const(char)[] s)
{
int year = parse!(ushort)(s);
enforce!MYXProtocol(s.skipOver("-"), `Expected: "-"`);
Expand All @@ -274,18 +277,18 @@ DateTime toDateTime(const(char)[] s)
int minute = parse!(ubyte)(s);
enforce!MYXProtocol(s.skipOver(":"), `Expected: ":"`);
int second = parse!(ubyte)(s);
return DateTime(year, month, day, hour, minute, second);
return DateTimeExt(DateTime(year, month, day, hour, minute, second),0);
}

/++
Function to extract a DateTime from a ulong.
Function to extract a DateTimeExt from a ulong.

This is used to support the TimeStamp struct.

Params: x = A ulong e.g. 20111111122002UL.
Returns: A populated `std.datetime.DateTime` struct.
+/
DateTime toDateTime(ulong x)
DateTimeExt toDateTime(ulong x)
{
int second = cast(int) (x%100);
x /= 100;
Expand All @@ -302,7 +305,7 @@ DateTime toDateTime(ulong x)
enforce!MYXProtocol(year >= 1970 && year < 2039, "Date/time out of range for 2 bit timestamp");
enforce!MYXProtocol(year != 2038 || (month < 1 && day < 19 && hour < 3 && minute < 14 && second < 7),
"Date/time out of range for 2 bit timestamp");
return DateTime(year, month, day, hour, minute, second);
return DateTimeExt(DateTime(year, month, day, hour, minute, second),0);
}

/++
Expand All @@ -314,11 +317,12 @@ and a minimal number of bytes to embody the data.
Params: dt = `std.datetime.DateTime` struct.
Returns: Packed ubyte[].
+/
ubyte[] pack(in DateTime dt) pure nothrow
ubyte[] pack(in DateTimeExt dt) pure nothrow
{
uint len = 1;
if (dt.year || dt.month || dt.day) len = 5;
if (dt.hour || dt.minute|| dt.second) len = 8;
if (dt.u_seconds) len = 12;
ubyte[] rv;
rv.length = len;
rv[0] = cast(ubyte)(rv.length - 1); // num bytes
Expand All @@ -329,12 +333,19 @@ ubyte[] pack(in DateTime dt) pure nothrow
rv[3] = cast(ubyte) dt.month;
rv[4] = cast(ubyte) dt.day;
}
if(len == 8)
if(len >= 8)
{
rv[5] = cast(ubyte) dt.hour;
rv[6] = cast(ubyte) dt.minute;
rv[7] = cast(ubyte) dt.second;
}
if (len >= 12)
{
rv[8] = cast(ubyte)(dt.u_seconds & 0x0ff);
rv[9] = cast(ubyte)((dt.u_seconds >> 8) & 0x0ff);
rv[10] = cast(ubyte)((dt.u_seconds >> 16) & 0x0ff);
rv[11] = cast(ubyte)((dt.u_seconds >> 24) & 0x0ff);
}
return rv;
}

Expand Down Expand Up @@ -435,7 +446,7 @@ do
return toDate(packet.consume(5));
}

DateTime consume(T:DateTime, ubyte N=T.sizeof)(ref ubyte[] packet) pure
DateTimeExt consume(T:DateTimeExt, ubyte N=T.sizeof)(ref ubyte[] packet) pure
in
{
assert(packet.length);
Expand All @@ -445,7 +456,7 @@ do
{
auto numBytes = packet.consume!ubyte();
if(numBytes == 0)
return DateTime();
return DateTimeExt(DateTime(),0);

enforce!MYXProtocol(numBytes >= 4, "Supplied packet is not large enough to store DateTime");

Expand All @@ -455,14 +466,19 @@ do
int hour = 0;
int minute = 0;
int second = 0;
int u_seconds = 0;
if(numBytes > 4)
{
enforce!MYXProtocol(numBytes >= 7, "Supplied packet is not large enough to store a DateTime with TimeOfDay");
hour = packet.consume!ubyte();
minute = packet.consume!ubyte();
second = packet.consume!ubyte();
}
return DateTime(year, month, day, hour, minute, second);
if (numBytes >= 11)
{
u_seconds = packet.consume!uint;
}
return DateTimeExt(DateTime(year, month, day, hour, minute, second),u_seconds);
}


Expand Down Expand Up @@ -535,6 +551,8 @@ do
T myto(T)(const(char)[] value)
{
static if(is(T == DateTime))
return toDateTime(value).dt;
else static if(is(T == DateTimeExt))
return toDateTime(value);
else static if(is(T == Date))
return toDate(value);
Expand Down Expand Up @@ -575,8 +593,8 @@ SQLValue consumeBinaryValueIfComplete(T, int N=T.sizeof)(ref ubyte[] packet, boo
{
SQLValue result;

// Length of DateTime packet is NOT DateTime.sizeof, it can be 1, 5 or 8 bytes
static if(is(T==DateTime))
// Length of DateTime packet is NOT DateTime.sizeof, it can be 1, 5 or 8 or 12 bytes
static if(is(T==DateTimeExt))
result.isIncomplete = packet.length < 1;
else
result.isIncomplete = packet.length < N;
Expand Down Expand Up @@ -669,15 +687,15 @@ SQLValue consumeIfComplete()(ref ubyte[] packet, SQLType sqlType, bool binary, b
case SQLType.DOUBLE:
return packet.consumeIfComplete!double(binary, unsigned);
case SQLType.TIMESTAMP:
return packet.consumeIfComplete!DateTime(binary, unsigned);
return packet.consumeIfComplete!DateTimeExt(binary, unsigned);
case SQLType.TIME:
return packet.consumeIfComplete!TimeOfDay(binary, unsigned);
case SQLType.YEAR:
return packet.consumeIfComplete!ushort(binary, unsigned);
case SQLType.DATE:
return packet.consumeIfComplete!Date(binary, unsigned);
case SQLType.DATETIME:
return packet.consumeIfComplete!DateTime(binary, unsigned);
return packet.consumeIfComplete!DateTimeExt(binary, unsigned);
case SQLType.VARCHAR:
case SQLType.ENUM:
case SQLType.SET:
Expand Down
43 changes: 39 additions & 4 deletions source/mysql/types.d
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/// Structures for MySQL types not built-in to D/Phobos.
module mysql.types;
import taggedalgebraic.taggedalgebraic;
import std.datetime : DateTime, TimeOfDay, Date;
import std.datetime : DateTime, TimeOfDay, Date, SysTime,TimeZone,usecs;
import std.typecons : Nullable;
import std.format;

/++
A simple struct to represent time difference.
Expand Down Expand Up @@ -33,6 +34,40 @@ struct Timestamp
ulong rep;
}

/++
A D struct to stand for a DateTime with sub second resolution

+/
struct DateTimeExt
{
DateTime dt;
alias dt this;
uint u_seconds;
string toString() pure nothrow @safe const
{
try
return dt.date.toISOExtString ~ " " ~ dt.timeOfDay.toISOExtString ~ format(".%06d",this.u_seconds);
catch(Exception e)
assert(0,"DateTimeExt.toString threw");
}
SysTime makeSysTime(return scope immutable TimeZone tz = null) @safe const scope
{
return SysTime(dt,usecs(this.u_seconds),tz);
}
DateTimeExt opAssign(const DateTime x) @safe
{
dt = x;
u_seconds = 0;
return this;
}
DateTime opCast(T)()
if (is(T == DateTime))
{
return dt;
}
}


private union _MYTYPE
{
@safeOnly:
Expand All @@ -54,7 +89,7 @@ private union _MYTYPE
long Long;
float Float;
double Double;
.DateTime DateTime;
DateTimeExt DateTime;
TimeOfDay Time;
.Timestamp Timestamp;
.Date Date;
Expand All @@ -74,7 +109,7 @@ private union _MYTYPE
const(long)* LongRef;
const(float)* FloatRef;
const(double)* DoubleRef;
const(.DateTime)* DateTimeRef;
const(DateTimeExt)* DateTimeRef;
const(TimeOfDay)* TimeRef;
const(.Date)* DateRef;
const(string)* TextRef;
Expand Down Expand Up @@ -209,7 +244,7 @@ package MySQLVal _toVal(Variant v)
enum FQN = T.stringof;
}

alias BasicTypes = AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, DateTime, TimeOfDay, Date, Timestamp);
alias BasicTypes = AliasSeq!(bool, byte, ubyte, short, ushort, int, uint, long, ulong, float, double, DateTimeExt, TimeOfDay, Date, Timestamp);
alias ArrayTypes = AliasSeq!(char[], const(char)[],
ubyte[], const(ubyte)[], immutable(ubyte)[]);

Expand Down