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 {
|
||||
items: []services.notes.Note,
|
||||
items: []services.notes.NoteDetailed,
|
||||
|
||||
prev_page: TimelineArgs,
|
||||
next_page: TimelineArgs,
|
||||
|
|
|
@ -13,6 +13,13 @@ pub const CreateError = error{
|
|||
DatabaseFailure,
|
||||
};
|
||||
|
||||
pub const ActorDetailed = struct {
|
||||
id: Uuid,
|
||||
username: []const u8,
|
||||
host: []const u8,
|
||||
created_at: DateTime,
|
||||
};
|
||||
|
||||
pub const LookupError = error{
|
||||
DatabaseFailure,
|
||||
};
|
||||
|
|
|
@ -14,6 +14,17 @@ pub const Note = struct {
|
|||
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{
|
||||
DatabaseFailure,
|
||||
};
|
||||
|
@ -81,7 +92,7 @@ pub const QueryArgs = struct {
|
|||
};
|
||||
|
||||
pub const QueryResult = struct {
|
||||
items: []Note,
|
||||
items: []NoteDetailed,
|
||||
|
||||
prev_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);
|
||||
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
|
||||
\\
|
||||
);
|
||||
|
@ -140,7 +153,7 @@ pub fn query(db: anytype, args: QueryArgs, alloc: std.mem.Allocator) !QueryResul
|
|||
};
|
||||
|
||||
const results = try db.queryRowsWithOptions(
|
||||
Note,
|
||||
NoteDetailed,
|
||||
try builder.terminate(),
|
||||
query_args,
|
||||
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.
|
||||
// row() must be called until it returns null, or the query may not complete
|
||||
// Must be deallocated by a call to finish()
|
||||
pub fn Results(comptime T: type) type {
|
||||
// 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 {
|
||||
const Self = @This();
|
||||
|
||||
|
@ -141,13 +189,16 @@ pub fn Results(comptime T: type) type {
|
|||
return Self{ .underlying = underlying, .column_indices = blk: {
|
||||
var indices: [fields.len]u15 = undefined;
|
||||
inline for (fields) |f, i| {
|
||||
indices[i] = if (!std.meta.trait.isTuple(T))
|
||||
underlying.columnIndex(f.name) catch {
|
||||
std.log.err("Could not find column index for field {s}", .{f.name});
|
||||
if (comptime std.meta.trait.isTuple(T)) {
|
||||
indices[i] = i;
|
||||
} 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;
|
||||
};
|
||||
}
|
||||
else
|
||||
i;
|
||||
}
|
||||
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
|
||||
// successfully allocate is defined at runtime. So we iterate over the entire field array and
|
||||
// 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| {
|
||||
// TODO: Causes compiler segfault. why?
|
||||
//const F = f.field_type;
|
||||
const F = @TypeOf(@field(result, f.name));
|
||||
@field(result, f.name) = row_val.get(F, self.column_indices[i], alloc) catch |err| {
|
||||
std.log.err("SQL: Error getting column {s} of type {}", .{ f.name, F });
|
||||
//const F = @TypeOf(@field(result, f.name));
|
||||
const F = std.meta.Child(FieldPtr(*@TypeOf(result), 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;
|
||||
};
|
||||
fields_allocated += 1;
|
||||
|
|
Loading…
Reference in a new issue