Skip to content

Commit

Permalink
datetime type
Browse files Browse the repository at this point in the history
  • Loading branch information
speed2exe committed Dec 25, 2023
1 parent 627e2c7 commit 0960283
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 67 deletions.
106 changes: 54 additions & 52 deletions integration_tests/client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -432,58 +432,60 @@ test "binary data types - string" {
}
}

// test "binary data types - temporal" {
// var c = Client.init(test_config);
// defer c.deinit();
//
// try queryExpectOk(&c, "CREATE DATABASE test");
// defer queryExpectOk(&c, "DROP DATABASE test") catch {};
//
// try queryExpectOk(&c,
// \\
// \\CREATE TABLE test.temporal_types_example (
// \\ event_time DATETIME(6) NOT NULL
// \\)
// );
// defer queryExpectOk(&c, "DROP TABLE test.temporal_types_example") catch {};
//
// const prep_res = try c.prepare(allocator, "INSERT INTO test.temporal_types_example VALUES (?)");
// defer prep_res.deinit(allocator);
// const prep_stmt = try prep_res.expect(.ok);
//
// const params = .{
// .{
// @as(DateTime, .{
// .year = 2023,
// .month = 11,
// .day = 30,
// .hour = 6,
// .minute = 50,
// .second = 58,
// .microsecond = 123456,
// }),
// },
// };
// inline for (params) |param| {
// const exe_res = try c.execute(allocator, &prep_stmt, param);
// defer exe_res.deinit(allocator);
// _ = try exe_res.expect(.ok);
// }
//
// {
// const res = try c.query(allocator, "SELECT * FROM test.temporal_types_example");
// defer res.deinit(allocator);
// const rows_iter = (try res.expect(.rows)).iter();
//
// const table = try rows_iter.collect(allocator);
// defer table.deinit(allocator);
//
// const expected: []const []const ?[]const u8 = &.{
// &.{""},
// };
// try std.testing.expectEqualDeep(expected, table.rows);
// }
// }
test "binary data types - temporal" {
var c = Client.init(test_config);
defer c.deinit();

try queryExpectOk(&c, "CREATE DATABASE test");
defer queryExpectOk(&c, "DROP DATABASE test") catch {};

try queryExpectOk(&c,
\\
\\CREATE TABLE test.temporal_types_example (
\\ event_time DATETIME(6) NOT NULL
\\)
);
defer queryExpectOk(&c, "DROP TABLE test.temporal_types_example") catch {};

const prep_res = try c.prepare(allocator, "INSERT INTO test.temporal_types_example VALUES (?)");
defer prep_res.deinit(allocator);
const prep_stmt = try prep_res.expect(.ok);

const params = .{
.{
@as(DateTime, .{
.year = 2023,
.month = 11,
.day = 30,
.hour = 6,
.minute = 50,
.second = 58,
.microsecond = 123456,
}),
},
};

inline for (params) |param| {
const exe_res = try c.execute(allocator, &prep_stmt, param);
defer exe_res.deinit(allocator);
_ = try exe_res.expect(.ok);
}

{
const res = try c.query(allocator, "SELECT * FROM test.temporal_types_example");
defer res.deinit(allocator);
const rows_iter = (try res.expect(.rows)).iter();

const table = try rows_iter.collect(allocator);
defer table.deinit(allocator);

const expected: []const []const ?[]const u8 = &.{
&.{"2023-11-30 06:50:58.123456"},
};

try std.testing.expectEqualDeep(expected, table.rows);
}
}

//
// SELECT CONCAT(?, ?) AS col1
Expand Down
42 changes: 42 additions & 0 deletions src/helper.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const protocol = @import("./protocol.zig");
const PacketReader = protocol.packet_reader.PacketReader;
const packet_writer = protocol.packet_writer;
const ColumnDefinition41 = protocol.column_definition.ColumnDefinition41;
const DateTime = @import("./temporal.zig").DateTime;

fn comptimeIntToUInt(
comptime Unsigned: type,
Expand All @@ -32,6 +33,14 @@ pub fn encodeBinaryParam(param: anytype, col_def: *const ColumnDefinition41, wri
const col_type: EnumFieldType = @enumFromInt(col_def.column_type);

switch (param_type_info) {
.Struct => {
switch (col_type) {
.MYSQL_TYPE_DATETIME => {
return try encodeDateTime(param, writer);
},
else => {},
}
},
.Null => return,
.Optional => {
if (param) |p| {
Expand Down Expand Up @@ -203,6 +212,39 @@ pub fn encodeBinaryParam(param: anytype, col_def: *const ColumnDefinition41, wri
return error.InvalidConversion;
}

// To save space the packet can be compressed:
// if year, month, day, hour, minutes, seconds and microseconds are all 0, length is 0 and no other field is sent.
// if hour, seconds and microseconds are all 0, length is 4 and no other field is sent.
// if microseconds is 0, length is 7 and micro_seconds is not sent.
// otherwise the length is 11
fn encodeDateTime(dt: DateTime, writer: anytype) !void {
if (dt.microsecond > 0) {
try packet_writer.writeUInt8(writer, 11);
try packet_writer.writeUInt16(writer, dt.year);
try packet_writer.writeUInt8(writer, dt.month);
try packet_writer.writeUInt8(writer, dt.day);
try packet_writer.writeUInt8(writer, dt.hour);
try packet_writer.writeUInt8(writer, dt.minute);
try packet_writer.writeUInt8(writer, dt.second);
try packet_writer.writeUInt32(writer, dt.microsecond);
} else if (dt.hour > 0 or dt.minute > 0 or dt.second > 0) {
try packet_writer.writeUInt8(writer, 7);
try packet_writer.writeUInt16(writer, dt.year);
try packet_writer.writeUInt8(writer, dt.month);
try packet_writer.writeUInt8(writer, dt.day);
try packet_writer.writeUInt8(writer, dt.hour);
try packet_writer.writeUInt8(writer, dt.minute);
try packet_writer.writeUInt8(writer, dt.second);
} else if (dt.year > 0 or dt.month > 0 or dt.day > 0) {
try packet_writer.writeUInt8(writer, 4);
try packet_writer.writeUInt16(writer, dt.year);
try packet_writer.writeUInt8(writer, dt.month);
try packet_writer.writeUInt8(writer, dt.day);
} else {
try packet_writer.writeUInt8(writer, 0);
}
}

pub fn scanTextResultRow(raw: []const u8, dest: []?[]const u8) !void {
var packet_reader = PacketReader.initFromPayload(raw);
for (dest) |*d| {
Expand Down
12 changes: 9 additions & 3 deletions src/protocol/prepared_statements.zig
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub const ExecuteRequest = struct {
iteration_count: u32 = 1, // Always 1
new_params_bind_flag: u8 = 1,

attributes: []const BinaryParam = &.{},
attributes: []const BinaryParam = &.{}, // Not supported yet

pub fn writeWithParams(e: *const ExecuteRequest, writer: anytype, params: anytype) !void {
try packet_writer.writeUInt8(writer, constants.COM_STMT_EXECUTE);
Expand All @@ -92,12 +92,18 @@ pub const ExecuteRequest = struct {
try writeNullBitmap(params, &.{}, writer);
}

const col_defs = e.prep_stmt.params;
if (params.len != col_defs.len) {
std.log.err("expected column count: {d}, but got {d}", .{ col_defs.len, params.len });
return error.ParamsCountNotMatch;
}

// If a statement is re-executed without changing the params types,
// the types do not need to be sent to the server again.
// send type to server (0 / 1)
try packet_writer.writeLengthEncodedInteger(writer, e.new_params_bind_flag);
if (e.new_params_bind_flag > 0) {
for (e.prep_stmt.params) |col_def| {
for (col_defs) |col_def| {
try packet_writer.writeUInt8(writer, col_def.column_type);

if (col_def.flags & constants.UNSIGNED_FLAG > 0) {
Expand All @@ -120,7 +126,7 @@ pub const ExecuteRequest = struct {

// TODO: Write params and attr as binary values
// Write params as binary values
inline for (params, e.prep_stmt.params) |param, *col_def| {
inline for (params, col_defs) |param, *col_def| {
try helper.encodeBinaryParam(param, col_def, writer);
}
if (has_attributes_to_write) {
Expand Down
24 changes: 12 additions & 12 deletions src/temporal.zig
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
// Type for MYSQL_TYPE_DATE, MYSQL_TYPE_DATETIME and MYSQL_TYPE_TIMESTAMP, i.e. When was it?
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html#sect_protocol_binary_resultset_row_value_date
pub const DateTime = struct {
year: u16,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
year: u16 = 0,
month: u8 = 0,
day: u8 = 0,
hour: u8 = 0,
minute: u8 = 0,
second: u8 = 0,
microsecond: u32 = 0,
};

// Type for MYSQL_TYPE_TIME, i.e. How long did it take?
// `Time` is ambigious and confusing, `Duration` was chosen as the name instead
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html#sect_protocol_binary_resultset_row_value_time
pub const Duration = struct {
days: u32,
hour: u8,
minute: u8,
second: u8,
microsecond: u32,
days: u32 = 0,
hour: u8 = 0,
minute: u8 = 0,
second: u8 = 0,
microsecond: u32 = 0,
};

0 comments on commit 0960283

Please sign in to comment.