Handle inline relations in result parsing.
+ Parse inline relations when parsing a query result. + Implement a custom pg.Mapper to pass the row manually.
This commit is contained in:
parent
e33bc5aaa2
commit
24989603a3
4 changed files with 305 additions and 42 deletions
|
@ -59,8 +59,89 @@ pub fn handleRawPostgresqlError(err: anyerror, connection: *pg.Conn) anyerror {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn isSlice(comptime T: type) ?type {
|
||||||
|
switch(@typeInfo(T)) {
|
||||||
|
.Pointer => |ptr| {
|
||||||
|
if (ptr.size != .Slice) {
|
||||||
|
@compileError("cannot get value of type " ++ @typeName(T));
|
||||||
|
}
|
||||||
|
return if (ptr.child == u8) null else ptr.child;
|
||||||
|
},
|
||||||
|
.Optional => |opt| return isSlice(opt.child),
|
||||||
|
else => return null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mapValue(comptime T: type, value: T, allocator: std.mem.Allocator) !T {
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Optional => |opt| {
|
||||||
|
if (value) |v| {
|
||||||
|
return try mapValue(opt.child, v, allocator);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (T == []u8 or T == []const u8) {
|
||||||
|
return try allocator.dupe(u8, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.meta.hasFn(T, "pgzMoveOwner")) {
|
||||||
|
return value.pgzMoveOwner(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rowMapColumn(self: *const pg.Row, field: *const std.builtin.Type.StructField, optional_column_index: ?usize, allocator: ?std.mem.Allocator) !field.type {
|
||||||
|
const T = field.type;
|
||||||
|
const column_index = optional_column_index orelse {
|
||||||
|
if (field.default_value) |dflt| {
|
||||||
|
return @as(*align(1) const field.type, @ptrCast(dflt)).*;
|
||||||
|
}
|
||||||
|
return error.FieldColumnMismatch;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (comptime isSlice(T)) |S| {
|
||||||
|
const slice = blk: {
|
||||||
|
if (@typeInfo(T) == .Optional) {
|
||||||
|
break :blk self.get(?pg.Iterator(S), column_index) orelse return null;
|
||||||
|
} else {
|
||||||
|
break :blk self.get(pg.Iterator(S), column_index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return try slice.alloc(allocator orelse return error.AllocatorRequiredForSliceMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = self.get(field.type, column_index);
|
||||||
|
const a = allocator orelse return value;
|
||||||
|
return mapValue(T, value, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn PgMapper(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
result: *pg.Result,
|
||||||
|
allocator: ?std.mem.Allocator,
|
||||||
|
column_indexes: [std.meta.fields(T).len]?usize,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn next(self: *const Self, row: *pg.Row) !?T {
|
||||||
|
var value: T = undefined;
|
||||||
|
|
||||||
|
const allocator = self.allocator;
|
||||||
|
inline for (std.meta.fields(T), self.column_indexes) |field, optional_column_index| {
|
||||||
|
//TODO I must reimplement row.mapColumn because it's not public :-(
|
||||||
|
@field(value, field.name) = try rowMapColumn(row, &field, optional_column_index, allocator);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Make a PostgreSQL result mapper with the given prefix, if there is one.
|
/// Make a PostgreSQL result mapper with the given prefix, if there is one.
|
||||||
pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Allocator, optionalPrefix: ?[]const u8) !pg.Mapper(T) {
|
pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Allocator, optionalPrefix: ?[]const u8) !PgMapper(T) {
|
||||||
var column_indexes: [std.meta.fields(T).len]?usize = undefined;
|
var column_indexes: [std.meta.fields(T).len]?usize = undefined;
|
||||||
|
|
||||||
inline for (std.meta.fields(T), 0..) |field, i| {
|
inline for (std.meta.fields(T), 0..) |field, i| {
|
||||||
|
@ -80,21 +161,91 @@ pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Alloc
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PostgreSQL implementation of the query result reader.
|
||||||
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
||||||
const InstanceInterface = _result.QueryResultReader(TableShape, inlineRelations).Instance;
|
const InstanceInterface = _result.QueryResultReader(TableShape, inlineRelations).Instance;
|
||||||
|
|
||||||
|
// Build relations mappers container type.
|
||||||
|
const RelationsMappersType = comptime typeBuilder: {
|
||||||
|
if (inlineRelations) |_inlineRelations| {
|
||||||
|
// Make a field for each relation.
|
||||||
|
var fields: [_inlineRelations.len]std.builtin.Type.StructField = undefined;
|
||||||
|
|
||||||
|
for (_inlineRelations, &fields) |relation, *field| {
|
||||||
|
// Get relation field type (TableShape of the related value).
|
||||||
|
var relationImpl = relation.relation{};
|
||||||
|
const relationInstanceType = @TypeOf(relationImpl.relation());
|
||||||
|
const relationFieldType = PgMapper(relationInstanceType.TableShape);
|
||||||
|
|
||||||
|
field.* = .{
|
||||||
|
.name = relation.field ++ [0:0]u8{},
|
||||||
|
.type = relationFieldType,
|
||||||
|
.default_value = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = @alignOf(relationFieldType),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build type with one field for each relation.
|
||||||
|
break :typeBuilder @Type(std.builtin.Type{
|
||||||
|
.Struct = .{
|
||||||
|
.layout = std.builtin.Type.ContainerLayout.auto,
|
||||||
|
.fields = &fields,
|
||||||
|
.decls = &[0]std.builtin.Type.Declaration{},
|
||||||
|
.is_tuple = false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build default empty type.
|
||||||
|
break :typeBuilder @Type(std.builtin.Type{
|
||||||
|
.Struct = .{
|
||||||
|
.layout = std.builtin.Type.ContainerLayout.auto,
|
||||||
|
.fields = &[0]std.builtin.Type.StructField{},
|
||||||
|
.decls = &[0]std.builtin.Type.Declaration{},
|
||||||
|
.is_tuple = false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
/// PostgreSQL implementation of the query result reader instance.
|
||||||
pub const Instance = struct {
|
pub const Instance = struct {
|
||||||
/// Main object mapper.
|
/// Main object mapper.
|
||||||
mainMapper: pg.Mapper(TableShape) = undefined,
|
mainMapper: PgMapper(TableShape) = undefined,
|
||||||
|
relationsMappers: RelationsMappersType = undefined,
|
||||||
|
|
||||||
fn next(opaqueSelf: *anyopaque) !?TableShape { //TODO inline relations.
|
fn next(opaqueSelf: *anyopaque) !?_result.TableWithRelations(TableShape, inlineRelations) {
|
||||||
const self: *Instance = @ptrCast(@alignCast(opaqueSelf));
|
const self: *Instance = @ptrCast(@alignCast(opaqueSelf));
|
||||||
return try self.mainMapper.next();
|
|
||||||
|
// Try to get the next row.
|
||||||
|
var row: pg.Row = try self.mainMapper.result.next() orelse return null;
|
||||||
|
|
||||||
|
// Get main table result.
|
||||||
|
const mainTable = try self.mainMapper.next(&row) orelse return null;
|
||||||
|
|
||||||
|
// Initialize the result.
|
||||||
|
var result: _result.TableWithRelations(TableShape, inlineRelations) = undefined;
|
||||||
|
|
||||||
|
// Copy each basic table field.
|
||||||
|
inline for (std.meta.fields(TableShape)) |field| {
|
||||||
|
@field(result, field.name) = @field(mainTable, field.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inlineRelations) |_inlineRelations| {
|
||||||
|
// For each relation, retrieve its value and put it in the result.
|
||||||
|
inline for (_inlineRelations) |relation| {
|
||||||
|
//TODO detect null relation.
|
||||||
|
@field(result, relation.field) = try @field(self.relationsMappers, relation.field).next(&row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result; // Return built result.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the generic reader instance instance.
|
||||||
pub fn instance(self: *Instance, allocator: std.mem.Allocator) InstanceInterface {
|
pub fn instance(self: *Instance, allocator: std.mem.Allocator) InstanceInterface {
|
||||||
return .{
|
return .{
|
||||||
.__interface = .{
|
.__interface = .{
|
||||||
|
@ -115,9 +266,22 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
fn initInstance(opaqueSelf: *anyopaque, allocator: std.mem.Allocator) !InstanceInterface {
|
fn initInstance(opaqueSelf: *anyopaque, allocator: std.mem.Allocator) !InstanceInterface {
|
||||||
const self: *Self = @ptrCast(@alignCast(opaqueSelf));
|
const self: *Self = @ptrCast(@alignCast(opaqueSelf));
|
||||||
self.instance.mainMapper = try makeMapper(TableShape, self.result, allocator, null);
|
self.instance.mainMapper = try makeMapper(TableShape, self.result, allocator, null);
|
||||||
|
|
||||||
|
if (inlineRelations) |_inlineRelations| {
|
||||||
|
// Initialize mapper for each relation.
|
||||||
|
inline for (_inlineRelations) |relation| {
|
||||||
|
// Get relation field type (TableShape of the related value).
|
||||||
|
comptime var relationImpl = relation.relation{};
|
||||||
|
const relationInstanceType = @TypeOf(relationImpl.relation());
|
||||||
|
@field(self.instance.relationsMappers, relation.field) =
|
||||||
|
try makeMapper(relationInstanceType.TableShape, self.result, allocator, "relations." ++ relation.field ++ ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return self.instance.instance(allocator);
|
return self.instance.instance(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the generic reader instance.
|
||||||
pub fn reader(self: *Self) _result.QueryResultReader(TableShape, inlineRelations) {
|
pub fn reader(self: *Self) _result.QueryResultReader(TableShape, inlineRelations) {
|
||||||
return .{
|
return .{
|
||||||
._interface = .{
|
._interface = .{
|
||||||
|
|
|
@ -43,8 +43,8 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
|
||||||
|
|
||||||
for (_with) |relation| {
|
for (_with) |relation| {
|
||||||
// For each relation, determine if it's inline or not.
|
// For each relation, determine if it's inline or not.
|
||||||
var tt = relation.relation{};
|
var relationImpl = relation.relation{};
|
||||||
const relationInstance = tt.relation();
|
const relationInstance = relationImpl.relation();
|
||||||
if (relationInstance.inlineMapping()) {
|
if (relationInstance.inlineMapping()) {
|
||||||
// Add the current relation to inline relations.
|
// Add the current relation to inline relations.
|
||||||
inlineRelations = @ptrCast(@constCast(_comptime.append(inlineRelations, relation)));
|
inlineRelations = @ptrCast(@constCast(_comptime.append(inlineRelations, relation)));
|
||||||
|
|
|
@ -67,6 +67,10 @@ pub fn typedMany(
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
fn getRepositoryConfiguration(_: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable) {
|
||||||
|
return toRepositoryConfig;
|
||||||
|
}
|
||||||
|
|
||||||
fn inlineMapping(_: *anyopaque) bool {
|
fn inlineMapping(_: *anyopaque) bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -109,11 +113,12 @@ pub fn typedMany(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relation(self: *Self) Relation {
|
pub fn relation(self: *Self) Relation(ToModel, ToTable) {
|
||||||
return .{
|
return .{
|
||||||
._interface = .{
|
._interface = .{
|
||||||
.instance = self,
|
.instance = self,
|
||||||
|
|
||||||
|
.getRepositoryConfiguration = getRepositoryConfiguration,
|
||||||
.inlineMapping = inlineMapping,
|
.inlineMapping = inlineMapping,
|
||||||
.genJoin = genJoin,
|
.genJoin = genJoin,
|
||||||
.genSelect = genSelect,
|
.genSelect = genSelect,
|
||||||
|
@ -199,6 +204,10 @@ fn typedOne(
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
fn getRepositoryConfiguration(_: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable) {
|
||||||
|
return toRepositoryConfig;
|
||||||
|
}
|
||||||
|
|
||||||
fn inlineMapping(_: *anyopaque) bool {
|
fn inlineMapping(_: *anyopaque) bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -267,11 +276,12 @@ fn typedOne(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn relation(self: *Self) Relation {
|
pub fn relation(self: *Self) Relation(ToModel, ToTable) {
|
||||||
return .{
|
return .{
|
||||||
._interface = .{
|
._interface = .{
|
||||||
.instance = self,
|
.instance = self,
|
||||||
|
|
||||||
|
.getRepositoryConfiguration = getRepositoryConfiguration,
|
||||||
.inlineMapping = inlineMapping,
|
.inlineMapping = inlineMapping,
|
||||||
.genJoin = genJoin,
|
.genJoin = genJoin,
|
||||||
.genSelect = genSelect,
|
.genSelect = genSelect,
|
||||||
|
@ -282,42 +292,52 @@ fn typedOne(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Generic model relation interface.
|
/// Generic model relation interface.
|
||||||
pub const Relation = struct {
|
pub fn Relation(comptime ToModel: type, comptime ToTable: type) type {
|
||||||
const Self = @This();
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
_interface: struct {
|
pub const Model = ToModel;
|
||||||
instance: *anyopaque,
|
pub const TableShape = ToTable;
|
||||||
|
|
||||||
inlineMapping: *const fn (self: *anyopaque) bool,
|
_interface: struct {
|
||||||
genJoin: *const fn (self: *anyopaque, comptime alias: []const u8) []const u8,
|
instance: *anyopaque,
|
||||||
genSelect: *const fn (self: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8,
|
|
||||||
buildQuery: *const fn (self: *anyopaque, models: []const anyopaque, query: *anyopaque) anyerror!void,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Relation mapping is done inline: this means that it's done at the same time the model is mapped,
|
getRepositoryConfiguration: *const fn (self: *anyopaque) repository.RepositoryConfiguration(ToModel, ToTable),
|
||||||
/// and that the associated data will be retrieved in the main query.
|
inlineMapping: *const fn (self: *anyopaque) bool,
|
||||||
pub fn inlineMapping(self: Self) bool {
|
genJoin: *const fn (self: *anyopaque, comptime alias: []const u8) []const u8,
|
||||||
return self._interface.inlineMapping(self._interface.instance);
|
genSelect: *const fn (self: *anyopaque, comptime table: []const u8, comptime prefix: []const u8) []const u8,
|
||||||
}
|
buildQuery: *const fn (self: *anyopaque, models: []const anyopaque, query: *anyopaque) anyerror!void,
|
||||||
|
},
|
||||||
|
|
||||||
/// In case of inline mapping, generate a JOIN clause to retrieve the associated data.
|
/// Read the related model repository configuration.
|
||||||
pub fn genJoin(self: Self, comptime alias: []const u8) []const u8 {
|
pub fn getRepositoryConfiguration(self: Self) repository.RepositoryConfiguration(ToModel, ToTable) {
|
||||||
return self._interface.genJoin(self._interface.instance, alias);
|
return self._interface.getRepositoryConfiguration(self._interface.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a SELECT clause to retrieve the associated data, with the given table and prefix.
|
/// Relation mapping is done inline: this means that it's done at the same time the model is mapped,
|
||||||
pub fn genSelect(self: Self, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
/// and that the associated data will be retrieved in the main query.
|
||||||
return self._interface.genSelect(self._interface.instance, table, prefix);
|
pub fn inlineMapping(self: Self) bool {
|
||||||
}
|
return self._interface.inlineMapping(self._interface.instance);
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the query to retrieve relation data.
|
/// In case of inline mapping, generate a JOIN clause to retrieve the associated data.
|
||||||
/// Is always used when inline mapping is not possible, but also when loading relations lazily.
|
pub fn genJoin(self: Self, comptime alias: []const u8) []const u8 {
|
||||||
pub fn buildQuery(self: Self, models: []const anyopaque, query: *anyopaque) !void {
|
return self._interface.genJoin(self._interface.instance, alias);
|
||||||
return self._interface.buildQuery(self._interface.instance, models, query);
|
}
|
||||||
}
|
|
||||||
};
|
/// Generate a SELECT clause to retrieve the associated data, with the given table and prefix.
|
||||||
|
pub fn genSelect(self: Self, comptime table: []const u8, comptime prefix: []const u8) []const u8 {
|
||||||
|
return self._interface.genSelect(self._interface.instance, table, prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the query to retrieve relation data.
|
||||||
|
/// Is always used when inline mapping is not possible, but also when loading relations lazily.
|
||||||
|
pub fn buildQuery(self: Self, models: []const anyopaque, query: *anyopaque) !void {
|
||||||
|
return self._interface.buildQuery(self._interface.instance, models, query);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A model relation object.
|
/// A model relation object.
|
||||||
|
|
|
@ -3,9 +3,73 @@ const zollections = @import("zollections");
|
||||||
const _repository = @import("repository.zig");
|
const _repository = @import("repository.zig");
|
||||||
const _relations = @import("relations.zig");
|
const _relations = @import("relations.zig");
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generic interface of a query result reader.
|
/// Generic interface of a query result reader.
|
||||||
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
|
||||||
_ = inlineRelations;
|
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -13,12 +77,12 @@ pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[
|
||||||
pub const Instance = struct {
|
pub const Instance = struct {
|
||||||
__interface: struct {
|
__interface: struct {
|
||||||
instance: *anyopaque,
|
instance: *anyopaque,
|
||||||
next: *const fn (self: *anyopaque) anyerror!?TableShape, //TODO inline relations.
|
next: *const fn (self: *anyopaque) anyerror!?TableWithRelations(TableShape, inlineRelations),
|
||||||
},
|
},
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
pub fn next(self: Instance) !?TableShape {
|
pub fn next(self: Instance) !?TableWithRelations(TableShape, inlineRelations) {
|
||||||
return self.__interface.next(self.__interface.instance);
|
return self.__interface.next(self.__interface.instance);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -55,8 +119,23 @@ pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime re
|
||||||
while (try reader.next()) |rawModel| {
|
while (try reader.next()) |rawModel| {
|
||||||
// Parse each raw model from the reader.
|
// Parse each raw model from the reader.
|
||||||
const model = try allocator.create(Model);
|
const model = try allocator.create(Model);
|
||||||
model.* = try repositoryConfig.fromSql(rawModel);
|
model.* = try repositoryConfig.fromSql(toTableShape(TableShape, inlineRelations, rawModel));
|
||||||
//TODO inline relations.
|
|
||||||
|
// 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try models.append(model);
|
try models.append(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue