2024-11-23 18:18:41 +01:00
|
|
|
const std = @import("std");
|
|
|
|
const zollections = @import("zollections");
|
|
|
|
const _repository = @import("repository.zig");
|
|
|
|
const _relations = @import("relations.zig");
|
|
|
|
|
2024-11-24 01:53:00 +01:00
|
|
|
/// Type of a retrieved table data, with its retrieved relations.
|
|
|
|
pub fn TableWithRelations(comptime TableShape: type, comptime optionalRelations: ?[]const _relations.ModelRelation) type {
|
|
|
|
if (optionalRelations) |relations| {
|
|
|
|
const tableType = @typeInfo(TableShape);
|
|
|
|
|
|
|
|
// Build fields list: copy the existing table type fields and add those for relations.
|
|
|
|
var fields: [tableType.Struct.fields.len + relations.len]std.builtin.Type.StructField = undefined;
|
|
|
|
// Copy base table fields.
|
|
|
|
@memcpy(fields[0..tableType.Struct.fields.len], tableType.Struct.fields);
|
|
|
|
|
|
|
|
// For each relation, create a new struct field in the table shape.
|
|
|
|
for (relations, fields[tableType.Struct.fields.len..]) |relation, *field| {
|
|
|
|
// Get relation field type (optional TableShape of the related value).
|
|
|
|
comptime var relationImpl = relation.relation{};
|
|
|
|
const relationInstanceType = @TypeOf(relationImpl.relation());
|
|
|
|
const relationFieldType = @Type(std.builtin.Type{
|
|
|
|
.Optional = .{
|
|
|
|
.child = relationInstanceType.TableShape
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
// Create the new field from relation data.
|
|
|
|
field.* = std.builtin.Type.StructField{
|
|
|
|
.name = relation.field ++ [0:0]u8{},
|
|
|
|
.type = relationFieldType,
|
|
|
|
.default_value = null,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = @alignOf(relationFieldType),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the new type.
|
|
|
|
return @Type(std.builtin.Type{
|
|
|
|
.Struct = .{
|
|
|
|
.layout = tableType.Struct.layout,
|
|
|
|
.fields = &fields,
|
|
|
|
.decls = tableType.Struct.decls,
|
|
|
|
.is_tuple = tableType.Struct.is_tuple,
|
|
|
|
.backing_integer = tableType.Struct.backing_integer,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return TableShape;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convert a value of the fully retrieved type to the TableShape type.
|
|
|
|
pub fn toTableShape(comptime TableShape: type, comptime optionalRelations: ?[]const _relations.ModelRelation, value: TableWithRelations(TableShape, optionalRelations)) TableShape {
|
|
|
|
if (optionalRelations) |_| {
|
|
|
|
// Make a structure of TableShape type.
|
|
|
|
var tableValue: TableShape = undefined;
|
|
|
|
|
|
|
|
// Copy all fields of the table shape in the new structure.
|
|
|
|
inline for (std.meta.fields(TableShape)) |field| {
|
|
|
|
@field(tableValue, field.name) = @field(value, field.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the simplified structure.
|
|
|
|
return tableValue;
|
|
|
|
} else {
|
|
|
|
// No relations, it should already be of type TableShape.
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-23 18:18:41 +01:00
|
|
|
/// Generic interface of a query result reader.
|
|
|
|
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
|
|
|
return struct {
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
/// Generic interface of a query result reader instance.
|
|
|
|
pub const Instance = struct {
|
|
|
|
__interface: struct {
|
|
|
|
instance: *anyopaque,
|
2024-11-24 01:53:00 +01:00
|
|
|
next: *const fn (self: *anyopaque) anyerror!?TableWithRelations(TableShape, inlineRelations),
|
2024-11-23 18:18:41 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
|
2024-11-24 01:53:00 +01:00
|
|
|
pub fn next(self: Instance) !?TableWithRelations(TableShape, inlineRelations) {
|
2024-11-23 18:18:41 +01:00
|
|
|
return self.__interface.next(self.__interface.instance);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
_interface: struct {
|
|
|
|
instance: *anyopaque,
|
|
|
|
init: *const fn (self: *anyopaque, allocator: std.mem.Allocator) anyerror!Instance,
|
|
|
|
},
|
|
|
|
|
|
|
|
/// Initialize a reader instance.
|
|
|
|
pub fn init(self: Self, allocator: std.mem.Allocator) !Instance {
|
|
|
|
return self._interface.init(self._interface.instance, allocator);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Map query result to repository model structures, and load the given relations.
|
|
|
|
pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime repositoryConfig: _repository.RepositoryConfiguration(Model, TableShape), comptime inlineRelations: ?[]const _relations.ModelRelation, comptime relations: ?[]const _relations.ModelRelation) type {
|
|
|
|
_ = relations;
|
|
|
|
return struct {
|
|
|
|
/// Map the query result to a repository result, with all the required relations.
|
|
|
|
pub fn map(allocator: std.mem.Allocator, queryReader: QueryResultReader(TableShape, inlineRelations)) !_repository.RepositoryResult(Model) {
|
|
|
|
// Create an arena for mapper data.
|
|
|
|
var mapperArena = std.heap.ArenaAllocator.init(allocator);
|
|
|
|
|
|
|
|
// Initialize query result reader.
|
|
|
|
const reader = try queryReader.init(mapperArena.allocator());
|
|
|
|
|
|
|
|
// Initialize models list.
|
|
|
|
var models = std.ArrayList(*Model).init(allocator);
|
|
|
|
defer models.deinit();
|
|
|
|
|
|
|
|
// Get all raw models from the result reader.
|
|
|
|
while (try reader.next()) |rawModel| {
|
|
|
|
// Parse each raw model from the reader.
|
|
|
|
const model = try allocator.create(Model);
|
2024-11-24 01:53:00 +01:00
|
|
|
model.* = try repositoryConfig.fromSql(toTableShape(TableShape, inlineRelations, rawModel));
|
|
|
|
|
|
|
|
// Map inline relations.
|
|
|
|
if (inlineRelations) |_inlineRelations| {
|
|
|
|
// If there are loaded inline relations, map them to the result.
|
|
|
|
inline for (_inlineRelations) |relation| {
|
|
|
|
comptime var relationImpl = relation.relation{};
|
|
|
|
const relationInstance = relationImpl.relation();
|
|
|
|
// Set the read inline relation value.
|
|
|
|
@field(model.*, relation.field) = (
|
|
|
|
if (@field(rawModel, relation.field)) |relationVal|
|
|
|
|
try relationInstance.getRepositoryConfiguration().fromSql(relationVal)
|
|
|
|
else null
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-23 18:18:41 +01:00
|
|
|
try models.append(model);
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO load relations?
|
|
|
|
|
|
|
|
// Return a result with the models.
|
|
|
|
return _repository.RepositoryResult(Model).init(allocator,
|
|
|
|
zollections.Collection(Model).init(allocator, try models.toOwnedSlice()),
|
|
|
|
mapperArena,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|