2024-11-23 18:18:41 +01:00
|
|
|
const std = @import("std");
|
|
|
|
const zollections = @import("zollections");
|
2024-11-25 13:02:59 +01:00
|
|
|
const _database = @import("database.zig");
|
2024-11-23 18:18:41 +01:00
|
|
|
const _repository = @import("repository.zig");
|
|
|
|
const _relations = @import("relations.zig");
|
|
|
|
|
2024-11-25 13:02:59 +01:00
|
|
|
/// Structure of a model with its metadata.
|
|
|
|
pub fn ModelWithMetadata(comptime Model: type, comptime MetadataShape: ?type) type {
|
|
|
|
if (MetadataShape) |MetadataType| {
|
|
|
|
return struct {
|
|
|
|
model: Model,
|
|
|
|
metadata: MetadataType,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return Model;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-24 01:53:00 +01:00
|
|
|
/// Type of a retrieved table data, with its retrieved relations.
|
2024-11-25 18:41:29 +01:00
|
|
|
pub fn TableWithRelations(comptime TableShape: type, comptime MetadataShape: ?type, comptime optionalRelations: ?[]const _relations.Relation) type {
|
2024-11-24 01:53:00 +01:00
|
|
|
if (optionalRelations) |relations| {
|
|
|
|
const tableType = @typeInfo(TableShape);
|
|
|
|
|
|
|
|
// Build fields list: copy the existing table type fields and add those for relations.
|
2024-11-25 13:02:59 +01:00
|
|
|
var fields: [tableType.Struct.fields.len + relations.len + (if (MetadataShape) |_| 1 else 0)]std.builtin.Type.StructField = undefined;
|
2024-11-24 01:53:00 +01:00
|
|
|
// 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.
|
2024-11-25 13:02:59 +01:00
|
|
|
for (relations, fields[tableType.Struct.fields.len..(tableType.Struct.fields.len+relations.len)]) |relation, *field| {
|
2024-11-24 01:53:00 +01:00
|
|
|
// Get relation field type (optional TableShape of the related value).
|
|
|
|
const relationFieldType = @Type(std.builtin.Type{
|
|
|
|
.Optional = .{
|
2024-11-25 18:41:29 +01:00
|
|
|
.child = relation.TableShape
|
2024-11-24 01:53:00 +01:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-11-25 13:02:59 +01:00
|
|
|
if (MetadataShape) |MetadataType| {
|
|
|
|
// Add metadata field.
|
|
|
|
fields[tableType.Struct.fields.len + relations.len] = std.builtin.Type.StructField{
|
|
|
|
.name = "_zrm_metadata",
|
|
|
|
.type = MetadataType,
|
|
|
|
.default_value = null,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = @alignOf(MetadataType),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-11-24 01:53:00 +01:00
|
|
|
// Build the new type.
|
|
|
|
return @Type(std.builtin.Type{
|
|
|
|
.Struct = .{
|
|
|
|
.layout = tableType.Struct.layout,
|
|
|
|
.fields = &fields,
|
2024-11-25 23:12:21 +01:00
|
|
|
.decls = &[0]std.builtin.Type.Declaration{},
|
2024-11-24 01:53:00 +01:00
|
|
|
.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.
|
2024-11-25 18:41:29 +01:00
|
|
|
pub fn toTableShape(comptime TableShape: type, comptime MetadataShape: ?type, comptime optionalRelations: ?[]const _relations.Relation, value: TableWithRelations(TableShape, MetadataShape, optionalRelations)) TableShape {
|
2024-11-24 01:53:00 +01:00
|
|
|
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.
|
2024-11-25 18:41:29 +01:00
|
|
|
pub fn QueryResultReader(comptime TableShape: type, comptime MetadataShape: ?type, comptime inlineRelations: ?[]const _relations.Relation) type {
|
2024-11-23 18:18:41 +01:00
|
|
|
return struct {
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
/// Generic interface of a query result reader instance.
|
|
|
|
pub const Instance = struct {
|
|
|
|
__interface: struct {
|
|
|
|
instance: *anyopaque,
|
2024-11-25 13:02:59 +01:00
|
|
|
next: *const fn (self: *anyopaque) anyerror!?TableWithRelations(TableShape, MetadataShape, inlineRelations),
|
2024-11-23 18:18:41 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
|
2024-11-25 13:02:59 +01:00
|
|
|
pub fn next(self: Instance) !?TableWithRelations(TableShape, MetadataShape, 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.
|
2024-11-25 18:41:29 +01:00
|
|
|
pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime MetadataShape: ?type, comptime repositoryConfig: _repository.RepositoryConfiguration(Model, TableShape), comptime inlineRelations: ?[]const _relations.Relation, comptime relations: ?[]const _relations.Relation) type {
|
2024-11-23 18:18:41 +01:00
|
|
|
return struct {
|
|
|
|
/// Map the query result to a repository result, with all the required relations.
|
2024-11-25 13:02:59 +01:00
|
|
|
pub fn map(comptime withMetadata: bool, allocator: std.mem.Allocator, connector: _database.Connector, queryReader: QueryResultReader(TableShape, MetadataShape, inlineRelations)) !_repository.RepositoryResult(if (withMetadata) ModelWithMetadata(Model, MetadataShape) else Model) {
|
|
|
|
// Get result type depending on metadata
|
|
|
|
const ResultType = if (withMetadata) ModelWithMetadata(Model, MetadataShape) else Model;
|
|
|
|
|
2024-11-23 18:18:41 +01:00
|
|
|
// 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.
|
2024-11-25 13:02:59 +01:00
|
|
|
var models = std.ArrayList(*ResultType).init(allocator);
|
2024-11-23 18:18:41 +01:00
|
|
|
defer models.deinit();
|
|
|
|
|
|
|
|
// Get all raw models from the result reader.
|
|
|
|
while (try reader.next()) |rawModel| {
|
|
|
|
// Parse each raw model from the reader.
|
2024-11-25 13:02:59 +01:00
|
|
|
const model = try allocator.create(ResultType);
|
|
|
|
(if (withMetadata) model.model else model.*) = try repositoryConfig.fromSql(toTableShape(TableShape, MetadataShape, inlineRelations, rawModel));
|
2024-11-24 01:53:00 +01:00
|
|
|
|
|
|
|
// Map inline relations.
|
|
|
|
if (inlineRelations) |_inlineRelations| {
|
|
|
|
// If there are loaded inline relations, map them to the result.
|
|
|
|
inline for (_inlineRelations) |relation| {
|
|
|
|
// Set the read inline relation value.
|
|
|
|
@field(model.*, relation.field) = (
|
|
|
|
if (@field(rawModel, relation.field)) |relationVal|
|
2024-11-25 18:41:29 +01:00
|
|
|
try relation.repositoryConfiguration().fromSql(relationVal)
|
2024-11-24 01:53:00 +01:00
|
|
|
else null
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-25 13:02:59 +01:00
|
|
|
if (withMetadata) {
|
|
|
|
// Set model metadata.
|
|
|
|
model.metadata = rawModel._zrm_metadata;
|
|
|
|
}
|
|
|
|
|
2024-11-23 18:18:41 +01:00
|
|
|
try models.append(model);
|
|
|
|
}
|
|
|
|
|
2024-11-25 13:02:59 +01:00
|
|
|
if (relations) |relationsToLoad| {
|
|
|
|
inline for (relationsToLoad) |relation| {
|
|
|
|
// Build query for the relation to get.
|
2024-11-25 18:41:29 +01:00
|
|
|
const query: *relation.QueryType = @ptrCast(@alignCast(
|
|
|
|
try relation.buildQuery(@ptrCast(models.items), allocator, connector)
|
2024-11-25 13:02:59 +01:00
|
|
|
));
|
|
|
|
defer {
|
|
|
|
query.deinit();
|
|
|
|
allocator.destroy(query);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get related models.
|
|
|
|
const relatedModels = try query.getWithMetadata(mapperArena.allocator());
|
|
|
|
|
|
|
|
// Create a map with related models.
|
|
|
|
const RelatedModelsListType = std.ArrayList(@TypeOf(relatedModels.models[0].model));
|
|
|
|
const RelatedModelsMapType = std.AutoHashMap(std.meta.FieldType(@TypeOf(relatedModels.models[0].metadata), .__zrm_relation_key), RelatedModelsListType);
|
|
|
|
var relatedModelsMap = RelatedModelsMapType.init(allocator);
|
|
|
|
defer relatedModelsMap.deinit();
|
|
|
|
|
|
|
|
// Fill the map of related models, indexing them by the relation key.
|
|
|
|
for (relatedModels.models) |relatedModel| {
|
|
|
|
// For each related model, put it in the map at the relation key.
|
|
|
|
var modelsList = try relatedModelsMap.getOrPut(relatedModel.metadata.__zrm_relation_key);
|
|
|
|
|
|
|
|
if (!modelsList.found_existing) {
|
|
|
|
// Initialize the related models list.
|
|
|
|
modelsList.value_ptr.* = RelatedModelsListType.init(mapperArena.allocator());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the current related model to the list.
|
|
|
|
try modelsList.value_ptr.append(relatedModel.model);
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each model, at the grouped related models if there are some.
|
|
|
|
for (models.items) |model| {
|
|
|
|
@field(model, relation.field) = (
|
|
|
|
if (relatedModelsMap.getPtr(@field(model, repositoryConfig.key[0]))) |relatedModelsList|
|
|
|
|
// There are related models, set them.
|
|
|
|
try relatedModelsList.toOwnedSlice()
|
|
|
|
else
|
|
|
|
// No related models, set an empty array.
|
|
|
|
&[0](@TypeOf(relatedModels.models[0].model)){}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-11-23 18:18:41 +01:00
|
|
|
|
|
|
|
// Return a result with the models.
|
2024-11-25 13:02:59 +01:00
|
|
|
return _repository.RepositoryResult(ResultType).init(allocator,
|
|
|
|
zollections.Collection(ResultType).init(allocator, try models.toOwnedSlice()),
|
2024-11-23 18:18:41 +01:00
|
|
|
mapperArena,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|