Skip to content

Commit

Permalink
feat: improve result usage ergonomics
Browse files Browse the repository at this point in the history
  • Loading branch information
speed2exe committed Nov 23, 2023
1 parent 536cb31 commit 4626b9c
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 80 deletions.
136 changes: 74 additions & 62 deletions integration_tests/client.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,57 +11,18 @@ test "ping" {
try c.ping(allocator);
}

fn expectRows(value: anytype) !@TypeOf(value.rows) {
return switch (value) {
.rows => |rows| rows,
else => errorUnexpectedValue(value),
};
}

fn expectErr(value: anytype) !@TypeOf(value.err) {
return switch (value) {
.err => |err| err,
else => return errorUnexpectedValue(value),
};
}

fn expectOk(value: anytype) !@TypeOf(value.ok) {
return switch (value) {
.ok => |ok| ok,
else => return errorUnexpectedValue(value),
};
}

fn errorUnexpectedValue(value: anytype) error{ ErrorPacket, UnexpectedValue } {
switch (value) {
.err => |err| return errorErrorPacket(&err),
else => |x| {
std.log.err("unexpected value: {any}\n", .{x});
return error.UnexpectedValue;
},
}
}

fn errorErrorPacket(err: *const ErrorPacket) error{ErrorPacket} {
std.log.err(
"got error packet: (code: {d}, message: {s})",
.{ err.error_code, err.error_message },
);
return error.ErrorPacket;
}

test "query database create and drop" {
var c = Client.init(test_config);
defer c.deinit();
{
const qr = try c.query(allocator, "CREATE DATABASE testdb");
defer qr.deinit(allocator);
_ = try expectOk(qr.value);
_ = try qr.expect(.ok);
}
{
const qr = try c.query(allocator, "DROP DATABASE testdb");
defer qr.deinit(allocator);
_ = try expectOk(qr.value);
_ = try qr.expect(.ok);
}
}

Expand All @@ -71,7 +32,7 @@ test "query syntax error" {

const qr = try c.query(allocator, "garbage query");
defer qr.deinit(allocator);
_ = try expectErr(qr.value);
_ = try qr.expect(.err);
}

test "query text protocol" {
Expand All @@ -82,7 +43,7 @@ test "query text protocol" {
const query_res = try c.query(allocator, "SELECT 1");
defer query_res.deinit(allocator);

const rows = (try expectRows(query_res.value)).iter();
const rows = (try query_res.expect(.rows)).iter();
var dest = [_]?[]const u8{undefined};
while (try rows.next(allocator)) |row| {
defer row.deinit(allocator);
Expand All @@ -93,7 +54,7 @@ test "query text protocol" {
{
const query_res = try c.query(allocator, "SELECT 3,4");
defer query_res.deinit(allocator);
const rows = (try expectRows(query_res.value)).iter();
const rows = (try query_res.expect(.rows)).iter();

var dest = [_]?[]const u8{ undefined, undefined };
while (try rows.next(allocator)) |row| {
Expand All @@ -106,7 +67,7 @@ test "query text protocol" {
{
const query_res = try c.query(allocator, "SELECT 5,null,7");
defer query_res.deinit(allocator);
const rows = (try expectRows(query_res.value)).iter();
const rows = (try query_res.expect(.rows)).iter();
var dest = [_]?[]const u8{ undefined, undefined, undefined };
while (try rows.next(allocator)) |row| {
defer row.deinit(allocator);
Expand All @@ -119,7 +80,7 @@ test "query text protocol" {
{
const query_res = try c.query(allocator, "SELECT 8,9 UNION ALL SELECT 10,11");
defer query_res.deinit(allocator);
const rows = try expectRows(query_res.value);
const rows = try query_res.expect(.rows);

var dest = [_]?[]const u8{ undefined, undefined };
{
Expand All @@ -141,8 +102,8 @@ test "query text protocol" {
defer row.deinit(std.testing.allocator);
switch (row.value) {
.eof => {},
.err => return errorUnexpectedValue(row.value),
.raw => return errorUnexpectedValue(row.value),
.err => |err| return err.asError(),
.raw => @panic("unexpected raw"),
}
}
}
Expand All @@ -155,7 +116,7 @@ test "query text table" {
{
const query_res = try c.query(allocator, "SELECT 1,2,3 UNION ALL SELECT 4,null,6");
defer query_res.deinit(allocator);
const iter = (try expectRows(query_res.value)).iter();
const iter = (try query_res.expect(.rows)).iter();
const table = try iter.collect(allocator);
defer table.deinit(allocator);
try std.testing.expectEqual(table.rows.len, 2);
Expand All @@ -180,7 +141,7 @@ test "prepare check" {
{ // prepare no execute
const prep_res = try c.prepare(allocator, "CREATE TABLE default.testtable (id INT, name VARCHAR(255))");
defer prep_res.deinit(allocator);
_ = try expectOk(prep_res.value);
_ = try prep_res.expect(.ok);
}
{ // prepare with params
const prep_res = try c.prepare(allocator, "SELECT CONCAT(?, ?) as my_col");
Expand All @@ -191,7 +152,7 @@ test "prepare check" {
try std.testing.expectEqual(prep_stmt.prep_ok.num_params, 2);
try std.testing.expectEqual(prep_stmt.prep_ok.num_columns, 1);
},
else => return errorUnexpectedValue(prep_res.value),
.err => |err| return err.asError(),
}
try std.testing.expectEqual(c.conn.reader.pos, c.conn.reader.len);
}
Expand All @@ -203,18 +164,18 @@ test "prepare execute - 1" {
{
const prep_res = try c.prepare(allocator, "CREATE DATABASE testdb2");
defer prep_res.deinit(allocator);
const prep_stmt = try expectOk(prep_res.value);
const prep_stmt = try prep_res.expect(.ok);
const query_res = try c.execute(allocator, &prep_stmt);
defer query_res.deinit(allocator);
_ = try expectOk(query_res.value);
_ = try query_res.expect(.ok);
}
{
const prep_res = try c.prepare(allocator, "DROP DATABASE testdb2");
defer prep_res.deinit(allocator);
const prep_ok = try expectOk(prep_res.value);
const prep_ok = try prep_res.expect(.ok);
const query_res = try c.execute(allocator, &prep_ok);
defer query_res.deinit(allocator);
_ = try expectOk(query_res.value);
_ = try query_res.expect(.ok);
}
}

Expand All @@ -224,21 +185,21 @@ test "prepare execute - 2" {

const prep_res_1 = try c.prepare(allocator, "CREATE DATABASE testdb3");
defer prep_res_1.deinit(allocator);
const prep_stmt_1 = try expectOk(prep_res_1.value);
const prep_stmt_1 = try prep_res_1.expect(.ok);

const prep_res_2 = try c.prepare(allocator, "DROP DATABASE testdb3");
defer prep_res_2.deinit(allocator);
const prep_stmt_2 = try expectOk(prep_res_2.value);
const prep_stmt_2 = try prep_res_2.expect(.ok);

{
const query_res = try c.execute(allocator, &prep_stmt_1);
defer query_res.deinit(allocator);
_ = try expectOk(query_res.value);
_ = try query_res.expect(.ok);
}
{
const query_res = try c.execute(allocator, &prep_stmt_2);
defer query_res.deinit(allocator);
_ = try expectOk(query_res.value);
_ = try query_res.expect(.ok);
}
}

Expand All @@ -252,10 +213,10 @@ test "prepare execute with result" {
;
const prep_res = try c.prepare(allocator, query);
defer prep_res.deinit(allocator);
const prep_stmt = try expectOk(prep_res.value);
const prep_stmt = try prep_res.expect(.ok);
const query_res = try c.execute(allocator, &prep_stmt);
defer query_res.deinit(allocator);
const rows = (try expectRows(query_res.value)).iter();
const rows = (try query_res.expect(.rows)).iter();

const MyType = struct {
a: ?u8,
Expand All @@ -277,4 +238,55 @@ test "prepare execute with result" {
}
}

// SELECT CONCAT(?, ?) AS col1
//test "binary data types" {
// var c = Client.init(test_config);
// defer c.deinit();
//
// {
// const query =
// \\CREATE TABLE int_types_example (
// \\ tinyint_col TINYINT,
// \\ smallint_col SMALLINT,
// \\ mediumint_col MEDIUMINT,
// \\ int_col INT,
// \\ bigint_col BIGINT,
// \\ tinyint_unsigned_col TINYINT UNSIGNED,
// \\ smallint_unsigned_col SMALLINT UNSIGNED,
// \\ mediumint_unsigned_col MEDIUMINT UNSIGNED,
// \\ int_unsigned_col INT UNSIGNED,
// \\ bigint_unsigned_col BIGINT UNSIGNED
// \\);
// ;
//
// const res = try c.query(allocator, query);
// defer res.deinit(allocator);
//
//
// const prep_res = try c.prepare(allocator, query);
// defer prep_res.deinit(allocator);
// const prep_stmt = try expectOk(prep_res.value);
// const query_res = try c.execute(allocator, &prep_stmt);
// defer query_res.deinit(allocator);
// const rows = (try expectRows(query_res.value)).iter();
//
// const MyType = struct {
// a: u8,
// b: u16,
// // b: f64,
// };
// const expected = MyType{
// .a = 2,
// .b = 3,
// // .b = 0.4,
// };
//
// var dest: MyType = undefined;
// while (try rows.next(allocator)) |row| {
// defer row.deinit(allocator);
// try row.scanStruct(&dest);
// try std.testing.expectEqualDeep(expected, dest);
// }
// }
//}
//
//// SELECT CONCAT(?, ?) AS col1
30 changes: 18 additions & 12 deletions src/helper.zig
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ pub fn scanBinResRowtoStruct(dest: anytype, raw: []const u8, col_defs: []ColumnD
std.debug.assert(reader.finished());
}

inline fn logConversionError(comptime FieldType: type, field_name: []const u8, col_def: *const ColumnDefinition41, col_type: EnumFieldType) void {
inline fn logConversionError(comptime FieldType: type, field_name: []const u8, col_name: []const u8, col_type: EnumFieldType) void {
std.log.err(
"cannot convert to type({any}) of field({s}) from column({s}) of type({any})\n",
.{ FieldType, field_name, col_def.name, col_type },
"cannot convert from column(name: {s}, type: {any}) to field(name: {s}, type: {any})\n",
.{ col_name, col_type, field_name, FieldType },
);
}

Expand Down Expand Up @@ -104,19 +104,25 @@ inline fn binElemToValue(comptime FieldType: type, field_name: []const u8, col_d
else => {},
}
},
.Int => |int| {
_ = int;
switch (col_type) {
.MYSQL_TYPE_LONGLONG => {
return @intCast(reader.readUInt64());
},
else => {},
}
.Int => switch (col_type) {
.MYSQL_TYPE_LONGLONG => return @intCast(reader.readUInt64()),

.MYSQL_TYPE_LONG,
.MYSQL_TYPE_INT24,
=> return @intCast(reader.readUInt32()),

.MYSQL_TYPE_SHORT,
.MYSQL_TYPE_YEAR,
=> return @intCast(reader.readUInt16()),

.MYSQL_TYPE_TINY => return @intCast(reader.readUInt16()),

else => {},
},
else => {},
}

logConversionError(FieldType, field_name, col_def, col_type);
logConversionError(FieldType, field_name, col_def.name, col_type);
unreachable;
}

Expand Down
56 changes: 50 additions & 6 deletions src/result.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ const ResultSetIter = helper.ResultSetIter;

pub fn QueryResult(comptime ResultRowType: type) type {
return struct {
packet: Packet,
value: union(enum) {
const Value = union(enum) {
ok: OkPacket,
err: ErrorPacket,
rows: ResultSet(ResultRowType),
},
};

packet: Packet,
value: Value,

pub fn deinit(q: *const QueryResult(ResultRowType), allocator: std.mem.Allocator) void {
q.packet.deinit(allocator);
Expand All @@ -28,6 +30,28 @@ pub fn QueryResult(comptime ResultRowType: type) type {
else => {},
}
}

pub fn expect(
q: *const QueryResult(ResultRowType),
comptime value_variant: std.meta.FieldEnum(Value),
) !std.meta.FieldType(Value, value_variant) {
return switch (q.value) {
value_variant => @field(q.value, @tagName(value_variant)),
else => {
return switch (q.value) {
.err => |err| return err.asError(),
.ok => |ok| {
std.log.err("Unexpected OkPacket: {any}\n", .{ok});
return error.UnexpectedOk;
},
.rows => |rows| {
std.log.err("Unexpected ResultSet: {any}\n", .{rows});
return error.UnexpectedResultSet;
},
};
},
};
}
};
}

Expand Down Expand Up @@ -133,11 +157,13 @@ pub const BinaryResultRow = struct {
};

pub const PrepareResult = struct {
packet: Packet,
value: union(enum) {
const Value = union(enum) {
ok: PreparedStatement,
err: ErrorPacket,
},
};

packet: Packet,
value: Value,

pub fn deinit(p: *const PrepareResult, allocator: std.mem.Allocator) void {
p.packet.deinit(allocator);
Expand All @@ -146,6 +172,24 @@ pub const PrepareResult = struct {
else => {},
}
}

pub fn expect(
p: *const PrepareResult,
comptime value_variant: std.meta.FieldEnum(Value),
) !std.meta.FieldType(Value, value_variant) {
return switch (p.value) {
value_variant => @field(p.value, @tagName(value_variant)),
else => {
return switch (p.value) {
.err => |err| return err.asError(),
.ok => |ok| {
std.log.err("Unexpected OkPacket: {any}\n", .{ok});
return error.UnexpectedOk;
},
};
},
};
}
};

pub const PreparedStatement = struct {
Expand Down

0 comments on commit 4626b9c

Please sign in to comment.