Support for nested structs in sql layer
This commit is contained in:
parent
e4a04b869e
commit
305c5f8c92
4 changed files with 90 additions and 15 deletions
|
@ -85,7 +85,7 @@ pub const TimelineArgs = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const TimelineResult = struct {
|
pub const TimelineResult = struct {
|
||||||
items: []services.notes.Note,
|
items: []services.notes.NoteDetailed,
|
||||||
|
|
||||||
prev_page: TimelineArgs,
|
prev_page: TimelineArgs,
|
||||||
next_page: TimelineArgs,
|
next_page: TimelineArgs,
|
||||||
|
|
|
@ -13,6 +13,13 @@ pub const CreateError = error{
|
||||||
DatabaseFailure,
|
DatabaseFailure,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ActorDetailed = struct {
|
||||||
|
id: Uuid,
|
||||||
|
username: []const u8,
|
||||||
|
host: []const u8,
|
||||||
|
created_at: DateTime,
|
||||||
|
};
|
||||||
|
|
||||||
pub const LookupError = error{
|
pub const LookupError = error{
|
||||||
DatabaseFailure,
|
DatabaseFailure,
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,17 @@ pub const Note = struct {
|
||||||
created_at: DateTime,
|
created_at: DateTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const NoteDetailed = struct {
|
||||||
|
id: Uuid,
|
||||||
|
|
||||||
|
author: struct {
|
||||||
|
id: Uuid,
|
||||||
|
username: []const u8,
|
||||||
|
},
|
||||||
|
content: []const u8,
|
||||||
|
created_at: DateTime,
|
||||||
|
};
|
||||||
|
|
||||||
pub const CreateError = error{
|
pub const CreateError = error{
|
||||||
DatabaseFailure,
|
DatabaseFailure,
|
||||||
};
|
};
|
||||||
|
@ -81,7 +92,7 @@ pub const QueryArgs = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const QueryResult = struct {
|
pub const QueryResult = struct {
|
||||||
items: []Note,
|
items: []NoteDetailed,
|
||||||
|
|
||||||
prev_page: QueryArgs,
|
prev_page: QueryArgs,
|
||||||
next_page: QueryArgs,
|
next_page: QueryArgs,
|
||||||
|
@ -91,7 +102,9 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
|
||||||
var builder = sql.QueryBuilder.init(alloc);
|
var builder = sql.QueryBuilder.init(alloc);
|
||||||
defer builder.deinit();
|
defer builder.deinit();
|
||||||
|
|
||||||
try builder.appendSlice(selectStarFromNote ++
|
try builder.appendSlice(
|
||||||
|
\\SELECT note.id, note.content, note.created_at, actor.id AS "author.id", actor.username AS "author.username"
|
||||||
|
\\FROM note
|
||||||
\\ JOIN actor ON actor.id = note.author_id
|
\\ JOIN actor ON actor.id = note.author_id
|
||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
|
@ -140,7 +153,7 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
|
||||||
};
|
};
|
||||||
|
|
||||||
const results = try db.queryRowsWithOptions(
|
const results = try db.queryRowsWithOptions(
|
||||||
Note,
|
NoteDetailed,
|
||||||
try builder.terminate(),
|
try builder.terminate(),
|
||||||
query_args,
|
query_args,
|
||||||
max_items,
|
max_items,
|
||||||
|
|
|
@ -120,12 +120,60 @@ const RawResults = union(Engine) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn FieldPtr(comptime Ptr: type, comptime names: []const []const u8) type {
|
||||||
|
if (names.len == 0) return Ptr;
|
||||||
|
|
||||||
|
const T = std.meta.Child(Ptr);
|
||||||
|
|
||||||
|
const field = for (@typeInfo(T).Struct.fields) |f| {
|
||||||
|
if (std.mem.eql(u8, f.name, names[0])) break f;
|
||||||
|
} else @compileError("Unknown field " ++ names[0] ++ " in type " ++ @typeName(T));
|
||||||
|
|
||||||
|
return FieldPtr(*field.field_type, names[1..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fieldPtr(ptr: anytype, comptime names: []const []const u8) FieldPtr(@TypeOf(ptr), names) {
|
||||||
|
if (names.len == 0) return ptr;
|
||||||
|
|
||||||
|
return fieldPtr(&@field(ptr.*, names[0]), names[1..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isScalar(comptime T: type) bool {
|
||||||
|
if (comptime std.meta.trait.isZigString(T)) return true;
|
||||||
|
if (comptime std.meta.trait.isIntegral(T)) return true;
|
||||||
|
if (comptime std.meta.trait.isFloat(T)) return true;
|
||||||
|
if (comptime std.meta.trait.is(.Enum)(T)) return true;
|
||||||
|
if (T == bool) return true;
|
||||||
|
if (comptime std.meta.trait.hasFn("parse")(T)) return true;
|
||||||
|
|
||||||
|
if (comptime std.meta.trait.is(.Optional)(T) and isScalar(std.meta.Child(T))) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recursiveFieldPaths(comptime T: type, comptime prefix: []const []const u8) []const []const []const u8 {
|
||||||
|
comptime {
|
||||||
|
var fields: []const []const []const u8 = &.{};
|
||||||
|
|
||||||
|
for (std.meta.fields(T)) |f| {
|
||||||
|
const full_name = prefix ++ [_][]const u8{f.name};
|
||||||
|
if (isScalar(f.field_type)) {
|
||||||
|
fields = fields ++ [_][]const []const u8{full_name};
|
||||||
|
} else {
|
||||||
|
fields = fields ++ recursiveFieldPaths(f.field_type, full_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Represents a set of results.
|
// Represents a set of results.
|
||||||
// row() must be called until it returns null, or the query may not complete
|
// row() must be called until it returns null, or the query may not complete
|
||||||
// Must be deallocated by a call to finish()
|
// Must be deallocated by a call to finish()
|
||||||
pub fn Results(comptime T: type) type {
|
pub fn Results(comptime T: type) type {
|
||||||
// would normally make this a declaration of the struct, but it causes the compiler to crash
|
// would normally make this a declaration of the struct, but it causes the compiler to crash
|
||||||
const fields = if (T == void) .{} else std.meta.fields(T);
|
const fields = if (T == void) .{} else recursiveFieldPaths(T, &.{});
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -141,13 +189,16 @@ pub fn Results(comptime T: type) type {
|
||||||
return Self{ .underlying = underlying, .column_indices = blk: {
|
return Self{ .underlying = underlying, .column_indices = blk: {
|
||||||
var indices: [fields.len]u15 = undefined;
|
var indices: [fields.len]u15 = undefined;
|
||||||
inline for (fields) |f, i| {
|
inline for (fields) |f, i| {
|
||||||
indices[i] = if (!std.meta.trait.isTuple(T))
|
if (comptime std.meta.trait.isTuple(T)) {
|
||||||
underlying.columnIndex(f.name) catch {
|
indices[i] = i;
|
||||||
std.log.err("Could not find column index for field {s}", .{f.name});
|
} else {
|
||||||
|
const name = util.comptimeJoin(".", f);
|
||||||
|
indices[i] =
|
||||||
|
underlying.columnIndex(name) catch {
|
||||||
|
std.log.err("Could not find column index for field {s}", .{name});
|
||||||
return error.ColumnMismatch;
|
return error.ColumnMismatch;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else
|
|
||||||
i;
|
|
||||||
}
|
}
|
||||||
break :blk indices;
|
break :blk indices;
|
||||||
} };
|
} };
|
||||||
|
@ -168,15 +219,19 @@ pub fn Results(comptime T: type) type {
|
||||||
// Iteration bounds must be defined at comptime (inline for) but the number of fields we could
|
// Iteration bounds must be defined at comptime (inline for) but the number of fields we could
|
||||||
// successfully allocate is defined at runtime. So we iterate over the entire field array and
|
// successfully allocate is defined at runtime. So we iterate over the entire field array and
|
||||||
// conditionally deallocate fields in the loop.
|
// conditionally deallocate fields in the loop.
|
||||||
if (i < fields_allocated) util.deepFree(alloc, @field(result, f.name));
|
const ptr = fieldPtr(&result, f);
|
||||||
|
if (i < fields_allocated) util.deepFree(alloc, ptr.*);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (fields) |f, i| {
|
inline for (fields) |f, i| {
|
||||||
// TODO: Causes compiler segfault. why?
|
// TODO: Causes compiler segfault. why?
|
||||||
//const F = f.field_type;
|
//const F = f.field_type;
|
||||||
const F = @TypeOf(@field(result, f.name));
|
//const F = @TypeOf(@field(result, f.name));
|
||||||
@field(result, f.name) = row_val.get(F, self.column_indices[i], alloc) catch |err| {
|
const F = std.meta.Child(FieldPtr(*@TypeOf(result), f));
|
||||||
std.log.err("SQL: Error getting column {s} of type {}", .{ f.name, F });
|
const ptr = fieldPtr(&result, f);
|
||||||
|
const name = util.comptimeJoin(".", f);
|
||||||
|
ptr.* = row_val.get(F, self.column_indices[i], alloc) catch |err| {
|
||||||
|
std.log.err("SQL: Error getting column {s} of type {}", .{ name, F });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
fields_allocated += 1;
|
fields_allocated += 1;
|
||||||
|
|
Loading…
Reference in a new issue